xpra icon
Bug tracker and wiki

Ticket #1694: webp-fast.patch

File webp-fast.patch, 38.5 KB (added by Antoine Martin, 2 years ago)

re-adds webp and makes it fast!

  • setup.py

     
    185185enc_x264_ENABLED        = DEFAULT and pkg_config_ok("--exists", "x264")
    186186enc_x265_ENABLED        = DEFAULT and pkg_config_ok("--exists", "x265")
    187187pillow_ENABLED          = DEFAULT
     188webp_ENABLED            = DEFAULT and pkg_config_version("0.6", "libwebp")
    188189jpeg_ENABLED            = DEFAULT and pkg_config_version("1.4", "libturbojpeg")
    189190vpx_ENABLED             = DEFAULT and pkg_config_version("1.4", "vpx")
    190191enc_ffmpeg_ENABLED      = False
     
    214215#allow some of these flags to be modified on the command line:
    215216SWITCHES = ["enc_x264", "enc_x265", "enc_ffmpeg",
    216217            "nvenc", "cuda_kernels", "cuda_rebuild", "nvfbc",
    217             "vpx", "pillow", "jpeg",
     218            "vpx", "webp", "pillow", "jpeg",
    218219            "v4l2",
    219220            "dec_avcodec2", "csc_swscale",
    220221            "csc_libyuv",
     
    935936                   "xpra/codecs/v4l2/constants.pxi",
    936937                   "xpra/codecs/v4l2/pusher.c",
    937938                   "xpra/codecs/libav_common/av_log.c",
     939                   "xpra/codecs/webp/encode.c",
     940                   "xpra/codecs/webp/decode.c",
    938941                   "xpra/codecs/dec_avcodec2/decoder.c",
    939942                   "xpra/codecs/csc_libyuv/colorspace_converter.cpp",
    940943                   "xpra/codecs/csc_swscale/colorspace_converter.c",
     
    20622065if pillow_ENABLED:
    20632066    external_includes += ["PIL", "PIL.Image", "PIL.WebPImagePlugin"]
    20642067
     2068toggle_packages(webp_ENABLED, "xpra.codecs.webp")
     2069if webp_ENABLED:
     2070    webp_pkgconfig = pkgconfig("webp")
     2071    cython_add(Extension("xpra.codecs.webp.encode",
     2072                    ["xpra/codecs/webp/encode.pyx"],
     2073                    **webp_pkgconfig))
     2074    cython_add(Extension("xpra.codecs.webp.decode",
     2075                ["xpra/codecs/webp/decode.pyx"],
     2076                **webp_pkgconfig))
     2077
    20652078toggle_packages(jpeg_ENABLED, "xpra.codecs.jpeg")
    20662079if jpeg_ENABLED:
    20672080    jpeg_pkgconfig = pkgconfig("libturbojpeg")
  • tests/xpra/clients/test_change_settings.py

     
    4444        self.queue = []
    4545        for i in range(10):
    4646            self.queue.append(("command_request", "auto-refresh", "%.4f" % (i/100)))
    47             for encoding in ("rgb", "png", "jpeg", "h264", "vp8"):
     47            for encoding in ("rgb", "png", "jpeg", "h264", "vp8", "webp"):
    4848                self.queue.append(("command_request", "encoding", encoding, "strict"))
    4949                self.queue.append(("command_request", "quality", "*", 1))
    5050                self.queue.append(("command_request", "quality", "*", 100))
  • tests/xpra/codecs/test_Pillow_perf.py

     
    1313from io import BytesIO
    1414
    1515
    16 def do_test_encode(rgb_data, w, h, encodings=["png", "png/P", "png/L", "jpeg"], N=5, Q=[0, 50, 100], S=[0, 1, 50, 90, 100], has_alpha=False):
     16def do_test_encode(rgb_data, w, h, encodings=["png", "png/P", "png/L", "jpeg", "webp"], N=5, Q=[0, 50, 100], S=[0, 1, 50, 90, 100], has_alpha=False):
    1717    from xpra.codecs.pillow.encode import encode
    1818    from xpra.codecs.image_wrapper import ImageWrapper
    1919    image = ImageWrapper(0, 0, w, h, rgb_data, "BGRA", 32, w*4, planes=ImageWrapper.PACKED, thread_safe=True)
     
    2424        if encoding in ("png", "png/P", "png/L"):
    2525            Q_options = [-1]
    2626        S_options = S
     27        if encoding=="webp":
     28            S_options = [-1]
    2729        if encoding in ("jpeg"):
    2830            S_options = [0, -1]
    2931        for q in Q_options:
  • xpra/codecs/loader.py

     
    152152        codec_import_check("enc_pillow", "Pillow encoder", "xpra.codecs.pillow", "xpra.codecs.pillow.encode", "encode")
    153153        add_codec_version("enc_pillow", "xpra.codecs.pillow.encode")
    154154
     155        codec_import_check("enc_webp", "webp encoder", "xpra.codecs.webp", "xpra.codecs.webp.encode", "compress")
     156        add_codec_version("enc_webp", "xpra.codecs.webp.encode")
     157
    155158        codec_import_check("enc_jpeg", "JPEG decoder", "xpra.codecs.jpeg", "xpra.codecs.jpeg.encoder", "encoder")
    156159        add_codec_version("enc_jpeg", "xpra.codecs.jpeg.encoder")
    157160
     
    183186        codec_import_check("dec_pillow", "Pillow decoder", "xpra.codecs.pillow", "xpra.codecs.pillow.decode", "decode")
    184187        add_codec_version("dec_pillow", "xpra.codecs.pillow.decode")
    185188
     189        codec_import_check("dec_webp", "webp decoder", "xpra.codecs.webp", "xpra.codecs.webp.decode", "decompress")
     190        add_codec_version("dec_webp", "xpra.codecs.webp.decode")
     191
    186192        codec_import_check("dec_jpeg", "JPEG decoder", "xpra.codecs.jpeg", "xpra.codecs.jpeg.decoder", "decoder")
    187193        add_codec_version("dec_jpeg", "xpra.codecs.jpeg.decoder")
    188194
     
    228234
    229235
    230236CSC_CODECS = "csc_swscale", "csc_libyuv"
    231 ENCODER_CODECS = "enc_pillow", "enc_vpx", "enc_x264", "enc_x265", "nvenc", "enc_ffmpeg", "enc_jpeg"
    232 DECODER_CODECS = "dec_pillow", "dec_vpx", "dec_avcodec2", "dec_jpeg"
     237ENCODER_CODECS = "enc_pillow", "enc_vpx", "enc_webp", "enc_x264", "enc_x265", "nvenc", "enc_ffmpeg", "enc_jpeg"
     238DECODER_CODECS = "dec_pillow", "dec_vpx", "dec_webp", "dec_avcodec2", "dec_jpeg"
    233239
    234240ALL_CODECS = tuple(set(CSC_CODECS + ENCODER_CODECS + DECODER_CODECS))
    235241
    236242#note: this is just for defining the order of encodings,
    237243#so we have both core encodings (rgb24/rgb32) and regular encodings (rgb) in here:
    238 PREFERED_ENCODING_ORDER = ["h264", "vp9", "vp8", "mpeg4", "mpeg4+mp4", "h264+mp4", "mpeg4+mp4", "vp8+webm", "vp9+webm", "png", "png/P", "png/L", "rgb", "rgb24", "rgb32", "jpeg", "h265"]
     244PREFERED_ENCODING_ORDER = ["h264", "vp9", "vp8", "mpeg4", "mpeg4+mp4", "h264+mp4", "mpeg4+mp4", "vp8+webm", "vp9+webm", "png", "png/P", "png/L", "webp", "rgb", "rgb24", "rgb32", "jpeg", "h265"]
    239245#encoding order for edges (usually one pixel high or wide):
    240 EDGE_ENCODING_ORDER = ["rgb24", "rgb32", "jpeg", "png", "png/P", "png/L", "rgb"]
     246EDGE_ENCODING_ORDER = ["rgb24", "rgb32", "jpeg", "png", "webp", "png/P", "png/L", "rgb"]
    241247
    242248
    243249def get_rgb_compression_options():
     
    255261          "h265"    : "H.265",
    256262          "mpeg4"   : "MPEG4",
    257263          "vp8"     : "VP8",
     264      "webp"    : "WebP",
    258265          "vp9"     : "VP9",
    259266          "png"     : "PNG (24/32bpp)",
    260267          "png/P"   : "PNG (8bpp colour)",
     
    277284          "png"     : "Portable Network Graphics (lossless, 24bpp or 32bpp for transparency)",
    278285          "png/P"   : "Portable Network Graphics (lossy, 8bpp colour)",
    279286          "png/L"   : "Portable Network Graphics (lossy, 8bpp grayscale)",
     287          "webp"    : "WebP compression (supports lossless and lossy modes)",
    280288          "jpeg"    : "JPEG lossy compression",
    281289          "rgb"     : "Raw RGB pixels, lossless, compressed using %s (24bpp or 32bpp for transparency)" % (" or ".join(compressors)),
    282290          }.get(encoding)
    283291
    284 HELP_ORDER = ("auto", "h264", "h265", "vp8", "vp9", "mpeg4", "png", "png/P", "png/L", "rgb", "jpeg")
     292HELP_ORDER = ("auto", "h264", "h265", "vp8", "vp9", "mpeg4", "png", "png/P", "png/L", "webp", "rgb", "jpeg")
    285293
    286294#those are currently so useless that we don't want the user to select them by mistake
    287295PROBLEMATIC_ENCODINGS = ("h265", )
  • xpra/codecs/pillow/decode.py

     
    5353                       ('png/P',    "89504e470d0a1a0a0000000d494844520000002000000020080300000044a48ac600000300504c5445000000000000000000000000000000000000000000000000000000000000000000330000660000990000cc0000ff0000003300333300663300993300cc3300ff3300006600336600666600996600cc6600ff6600009900339900669900999900cc9900ff990000cc0033cc0066cc0099cc00cccc00ffcc0000ff0033ff0066ff0099ff00ccff00ffff00000033330033660033990033cc0033ff0033003333333333663333993333cc3333ff3333006633336633666633996633cc6633ff6633009933339933669933999933cc9933ff993300cc3333cc3366cc3399cc33cccc33ffcc3300ff3333ff3366ff3399ff33ccff33ffff33000066330066660066990066cc0066ff0066003366333366663366993366cc3366ff3366006666336666666666996666cc6666ff6666009966339966669966999966cc9966ff996600cc6633cc6666cc6699cc66cccc66ffcc6600ff6633ff6666ff6699ff66ccff66ffff66000099330099660099990099cc0099ff0099003399333399663399993399cc3399ff3399006699336699666699996699cc6699ff6699009999339999669999999999cc9999ff999900cc9933cc9966cc9999cc99cccc99ffcc9900ff9933ff9966ff9999ff99ccff99ffff990000cc3300cc6600cc9900cccc00ccff00cc0033cc3333cc6633cc9933cccc33ccff33cc0066cc3366cc6666cc9966cccc66ccff66cc0099cc3399cc6699cc9999cccc99ccff99cc00cccc33cccc66cccc99ccccccccccffcccc00ffcc33ffcc66ffcc99ffccccffccffffcc0000ff3300ff6600ff9900ffcc00ffff00ff0033ff3333ff6633ff9933ffcc33ffff33ff0066ff3366ff6666ff9966ffcc66ffff66ff0099ff3399ff6699ff9999ffcc99ffff99ff00ccff33ccff66ccff99ccffccccffffccff00ffff33ffff66ffff99ffffccffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023faca40000001549444154785e63601805a321301a02a321803d0400042000017854be5c0000000049454e44ae426082"),
    5454                       ('jpeg',     "ffd8ffe000104a46494600010100000100010000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc00011080020002003012200021101031101ffc4001500010100000000000000000000000000000007ffc40014100100000000000000000000000000000000ffc40014010100000000000000000000000000000000ffc40014110100000000000000000000000000000000ffda000c03010002110311003f009f800000000000ffd9"),
    5555                       ('jpeg',     "ffd8ffe000104a46494600010100000100010000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc00011080020002003012200021101031101ffc4001500010100000000000000000000000000000007ffc40014100100000000000000000000000000000000ffc40014010100000000000000000000000000000000ffc40014110100000000000000000000000000000000ffda000c03010002110311003f009f800000000000ffd9"),
     56                       ('webp',     "524946465c00000057454250565038580a000000100000001f00001f0000414c50480f00000001071011110012c2ffef7a44ff530f005650382026000000d002009d012a200020003ed162aa4fa825a3a2280801001a096900003da3a000fef39d800000"),
     57                       ('webp',     "524946465c00000057454250565038580a000000100000001f00001f0000414c50480f00000001071011110012c2ffef7a44ff530f005650382026000000d002009d012a200020003ed162aa4fa825a3a2280801001a096900003da3a000fef39d800000"),
    5658                       ):
    5759        if encoding not in ENCODINGS:
    5860            #removed already
  • xpra/codecs/pillow/encode.py

     
    2727def do_get_encodings():
    2828    log("PIL.Image.SAVE=%s", Image.SAVE)
    2929    encodings = []
    30     for encoding in ["png", "png/L", "png/P", "jpeg"]:
     30    for encoding in ["png", "png/L", "png/P", "jpeg", "webp"]:
    3131        #strip suffix (so "png/L" -> "png")
    3232        stripped = encoding.split("/")[0].upper()
    3333        if stripped in Image.SAVE:
     
    121121        raise
    122122    buf = BytesIOClass()
    123123    client_options = {}
    124     if coding=="jpeg":
     124    if coding in ("jpeg", "webp"):
    125125        #newer versions of pillow require explicit conversion to non-alpha:
    126126        if pixel_format.find("A")>=0:
    127127            im = im.convert("RGB")
    128         q = int(min(99, max(1, quality)))
     128        q = int(min(100, max(1, quality)))
    129129        kwargs = im.info
    130130        kwargs["quality"] = q
    131131        client_options["quality"] = q
    132         if speed<50:
     132        if coding=="jpeg" and speed<50:
    133133            #(optimizing jpeg is pretty cheap and worth doing)
    134134            kwargs["optimize"] = True
    135135            client_options["optimize"] = True
     136        if coding=="webp" and q>=100:
     137            kwargs["lossless"] = 1
    136138        pil_fmt = coding.upper()
    137139    else:
    138140        assert coding in ("png", "png/P", "png/L"), "unsupported encoding: %s" % coding
  • xpra/codecs/webp/decode.pyx

     
    11# This file is part of Xpra.
    2 # Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     2# Copyright (C) 2014-2017 Antoine Martin <antoine@devloop.org.uk>
    33# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    44# later version. See the file COPYING for details.
    55
     
    66from xpra.log import Logger
    77log = Logger("encoder", "webp")
    88
     9from xpra.buffers.membuf cimport memalign, memory_as_pybuffer
     10from xpra.os_util import bytestostr
    911
     12
    1013from libc.stdint cimport uint8_t, uint32_t
    1114
    1215cdef extern from *:
     
    1619    void free(void *ptr)
    1720
    1821
    19 cdef extern from "../../buffers/memalign.h":
    20     void *xmemalign(size_t size)
    21 
    22 cdef extern from "../../buffers/buffers.h":
    23     object memory_as_pybuffer(void* ptr, Py_ssize_t buf_len, int readonly)
    24     int get_buffer_api_version()
    25 
    2622cdef extern from "webp/decode.h":
    2723
    2824    int WebPGetDecoderVersion()
     
    149145    return  {
    150146            "version"      : get_version(),
    151147            "encodings"    : get_encodings(),
    152             "buffer_api"   : get_buffer_api_version()}
     148            }
    153149
    154150def webp_check(int ret):
    155151    if ret==0:
     
    211207        config.output.colorspace = MODE_RGB
    212208    cdef size_t size = stride * config.input.height
    213209    #allocate the buffer:
    214     cdef uint8_t *buffer = <uint8_t*> xmemalign(size + stride)      #add one line of padding
     210    cdef uint8_t *buffer = <uint8_t*> memalign(size + stride)      #add one line of padding
    215211    cdef WebpBufferWrapper b = WebpBufferWrapper(<unsigned long> buffer, size)
    216212    config.output.u.RGBA.rgba   = buffer
    217213    config.output.u.RGBA.stride = stride
  • xpra/codecs/webp/encode.pyx

     
    11# This file is part of Xpra.
    2 # Copyright (C) 2014, 2015 Antoine Martin <antoine@devloop.org.uk>
     2# Copyright (C) 2014-2017 Antoine Martin <antoine@devloop.org.uk>
    33# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    44# later version. See the file COPYING for details.
    55
     
    88from xpra.log import Logger
    99log = Logger("encoder", "webp")
    1010
     11from xpra.buffers.membuf cimport memalign, object_as_buffer
     12from xpra.os_util import bytestostr
     13
    1114from xpra.util import envbool
    1215cdef int LOG_CONFIG = envbool("XPRA_WEBP_LOG_CONFIG", False)
    13 cdef int USE_THREADS = envbool("XPRA_WEBP_THREADING", True)
     16cdef int WEBP_THREADING = envbool("XPRA_WEBP_THREADING", True)
    1417
    1518
    1619cdef inline int MIN(int a, int b):
     
    2326    return b
    2427
    2528
    26 from libc.stdint cimport uint8_t, uint32_t
     29from libc.stdint cimport uint8_t, uint32_t, uintptr_t
    2730
    2831cdef extern from "string.h":
    2932    void * memset ( void * ptr, int value, size_t num )
     
    3538    void free(void *ptr)
    3639
    3740
    38 cdef extern from "../../buffers/buffers.h":
    39     int    object_as_buffer(object obj, const void ** buffer, Py_ssize_t * buffer_len)
    40     int get_buffer_api_version()
     41DEF WEBP_MAX_DIMENSION = 16383
    4142
    4243cdef extern from "webp/encode.h":
    4344
     
    110111                                        #0: none, 1: fast, 2: best. Default if 1.
    111112        int alpha_quality               #Between 0 (smallest size) and 100 (lossless).
    112113                                        #Default is 100.
    113         int _pass                        #number of entropy-analysis passes (in [1..10]).
     114        int _pass "pass"                #number of entropy-analysis passes (in [1..10]).
    114115
    115116        int show_compressed             #if true, export the compressed picture back.
    116117                                        #In-loop filtering is not applied.
     
    322323    return  {
    323324            "version"       : get_version(),
    324325            "encodings"     : get_encodings(),
    325             "buffer_api"    : get_buffer_api_version(),
    326             "threading"     : bool(USE_THREADS),
     326            "threading"     : bool(WEBP_THREADING),
    327327            "image-hint"    : DEFAULT_IMAGE_HINT,
    328328            "image-hints"   : IMAGE_HINT.values(),
    329329            "preset"        : DEFAULT_PRESET,
     
    360360            "alpha_compression" : config.alpha_compression,
    361361            "alpha_filtering"   : config.alpha_filtering,
    362362            "alpha_quality"     : config.alpha_quality,
    363             #"pass"      : config._pass,
     363            "pass"              : config._pass,
    364364            "show_compressed"   : config.show_compressed,
    365365            "preprocessing"     : config.preprocessing,
    366366            "partitions"        : config.partitions,
     
    371371            }
    372372
    373373
    374 def compress(pixels, int width, int height, int stride=0, int quality=50, int speed=50, has_alpha=False):
     374def compress(pixels, int width, int height, int stride=0, int quality=50, int speed=50, has_alpha=False, int Bpp=4):
    375375    cdef uint8_t *pic_buf
    376376    cdef Py_ssize_t pic_buf_len = 0
    377377    cdef WebPConfig config
     
    378378    cdef WebPPreset preset = DEFAULT_PRESET
    379379    if width*height<8192:
    380380        preset = PRESET_SMALL
     381    if width>WEBP_MAX_DIMENSION or height>WEBP_MAX_DIMENSION:
     382        raise Exception("image too big: %ix%i" % (width, height))
    381383
     384    assert Bpp==4
    382385    cdef int ret = object_as_buffer(pixels, <const void**> &pic_buf, &pic_buf_len)
    383386    assert ret==0, "failed to get buffer from pixel object: %s (returned %s)" % (type(pixels), ret)
    384     log("webp.compress(%s bytes, %s, %s, %s, %s, %s, %s) buf=%#x", len(pixels), width, height, stride, quality, speed, has_alpha, <unsigned long> pic_buf)
    385     cdef int c = (stride or (width*4)) * height
    386     assert pic_buf_len>=c, "pixel buffer is too small: expected at least %s bytes but got %s" % (c, pic_buf_len)
     387    log("webp.compress(%i bytes, %i, %i, %i, %i, %i, %s, %i) buf=%#x", len(pixels), width, height, stride, quality, speed, has_alpha, Bpp, <uintptr_t> pic_buf)
     388    cdef int size = (stride or width) * Bpp * height
     389    assert pic_buf_len>=size, "pixel buffer is too small: expected at least %s bytes but got %s" % (size, pic_buf_len)
    387390
    388391    cdef int i
    389392    cdef int alpha_int = int(has_alpha)
     
    391394        #ensure webp will not decide to encode the alpha channel
    392395        #(this is stupid: we should be able to pass a flag instead)
    393396        i = 3
    394         while i<c:
     397        while i<size:
    395398            pic_buf[i] = 0xff
    396             i += 4
     399            i += Bpp
    397400
     401    ret = WebPConfigInit(&config)
     402    if not ret:
     403        raise Exception("failed to initialize webp config")
     404
    398405    ret = WebPConfigPreset(&config, preset, fclamp(quality))
    399406    if not ret:
    400         raise Exception("failed to initialise webp config")
     407        raise Exception("failed to set webp preset")
    401408
    402409    #tune it:
    403410    config.lossless = quality>=100
    404411    if config.lossless:
    405         config.quality = fclamp(100-speed)
     412        #not much to gain from setting a high quality here,
     413        #the latency will be higher for a negligible compression gain:
     414        config.quality = fclamp(50-speed//2)
    406415    else:
    407416        config.quality = fclamp(quality)
    408     config.method = MAX(0, MIN(6, 6-speed/16))
     417    #"method" takes values from 0 to 6,
     418    #but anything higher than 1 is dreadfully slow,
     419    #so only use method=1 when speed is already very low
     420    config.method = int(speed<10)
    409421    config.alpha_compression = alpha_int
    410422    config.alpha_filtering = MAX(0, MIN(2, speed/50)) * alpha_int
    411423    config.alpha_quality = quality * alpha_int
     424    if config.lossless:
     425        config.autofilter = 1
     426    else:
     427        config.segments = 1
     428        config.sns_strength = 0
     429        config.filter_strength = 0
     430        config.filter_sharpness = 7-quality//15
     431        config.filter_type = 0
     432        config.autofilter = 0
     433    config._pass = MAX(1, MIN(10, (100-speed)//10))
     434    config.preprocessing = int(speed<50)
    412435    config.image_hint = DEFAULT_IMAGE_HINT
    413     config.thread_level = USE_THREADS
     436    config.thread_level = WEBP_THREADING
    414437    config.partitions = 3
    415438    config.partition_limit = MAX(0, MIN(100, 100-quality))
    416439
    417440    log("webp.compress config: lossless=%s, quality=%s, method=%s, alpha=%s,%s,%s", config.lossless, config.quality, config.method,
    418441                    config.alpha_compression, config.alpha_filtering, config.alpha_quality)
    419     #config.sns_strength = 90
    420     #config.filter_sharpness = 6
    421442    ret = WebPValidateConfig(&config)
    422443    if not ret:
    423444        info = get_config_info(&config)
     
    428449    if not ret:
    429450        raise Exception("failed to initialise webp picture")
    430451
     452    #TODO: custom writer that over-allocates memory
    431453    cdef WebPMemoryWriter memory_writer
    432454    WebPMemoryWriterInit(&memory_writer)
    433455
     
    446468    cdata = memory_writer.mem[:memory_writer.size]
    447469    free(memory_writer.mem)
    448470    WebPPictureFree(&pic)
    449     log("webp.compress ratio=%i%%", 100*memory_writer.size//c)
     471    log("webp.compress ratio=%i%%", 100*memory_writer.size//size)
    450472    if LOG_CONFIG>0:
    451473        log("webp.compress used config: %s", get_config_info(&config))
    452474    return cdata
  • xpra/log.py

     
    202202                ("csc"          , "Colourspace conversion codecs"),
    203203                ("cuda"         , "CUDA device access (nvenc)"),
    204204                ("cython"       , "Cython CSC module"),
     205                ("opencl"       , "OpenCL CSC module"),
     206                ("opencv"       , "OpenCV CSC module"),
    205207                ("swscale"      , "swscale CSC module"),
    206208                ("libyuv"       , "libyuv CSC module"),
    207209                ("decoder"      , "All decoders"),
     
    216218                ("nvfbc"        , "nfbc screen capture"),
    217219                ("x264"         , "libx264 encoder"),
    218220                ("x265"         , "libx265 encoder"),
     221                ("xvid"         , "Xvid encoder"),
     222                ("webp"         , "libwebp encoder and decoder"),
    219223                ("webcam"       , "webcam access"),
    220224                ])),
    221225    ("Pointer", OrderedDict([
  • xpra/server/picture_encode.py

     
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
     7from math import sqrt
    78from xpra.log import Logger
    89log = Logger("window", "encoding")
    910
     
    1011from xpra.net import compression
    1112from xpra.codecs.argb.argb import bgra_to_rgb, bgra_to_rgba, argb_to_rgb, argb_to_rgba, r210_to_rgbx, r210_to_rgb, bgr565_to_rgbx, bgr565_to_rgb  #@UnresolvedImport
    1213from xpra.codecs.loader import get_codec
     14from xpra.util import envbool
    1315from xpra.os_util import memoryview_to_bytes, bytestostr, strtobytes, monotonic_time
    1416#"pixels_to_bytes" gets patched up by the OSX shadow server
    1517pixels_to_bytes = memoryview_to_bytes
     
    1820except:
    1921    mmap_write = None               #no mmap
    2022
     23WEBP_PILLOW = envbool("XPRA_WEBP_PILLOW", False)
    2124
     25
    2226#give warning message just once per key then ignore:
    2327encoding_warnings = set()
    2428def warn_encoding_once(key, message):
     
    2832        encoding_warnings.add(key)
    2933
    3034
     35def webp_encode(coding, image, rgb_formats, supports_transparency, quality, speed, options):
     36    log.info("webp_encode%s", (coding, image, rgb_formats, supports_transparency, quality, speed, options))
     37    pixel_format = image.get_pixel_format()
     38    #log("rgb_encode%s pixel_format=%s, rgb_formats=%s", (coding, image, rgb_formats, supports_transparency, speed, rgb_zlib, rgb_lz4), pixel_format, rgb_formats)
     39    if pixel_format not in rgb_formats:
     40        if not rgb_reformat(image, rgb_formats, supports_transparency):
     41            raise Exception("cannot find compatible rgb format to use for %s! (supported: %s)" % (pixel_format, rgb_formats))
     42        #get the new format:
     43        pixel_format = image.get_pixel_format()
     44        log.info("webp_encode reformat to %s", pixel_format)
     45    stride = image.get_rowstride()
     46    enc_webp = get_codec("enc_webp")
     47    #log("webp_encode%s stride=%s, enc_webp=%s", (coding, image, rgb_formats, supports_transparency, quality, speed, options), stride, enc_webp)
     48    if not WEBP_PILLOW and enc_webp and stride>0 and stride%4==0 and image.get_pixel_format() in ("BGRA", "BGRX", "RGBA", "RGBX"):
     49        #prefer Cython module:
     50        alpha = supports_transparency and image.get_pixel_format().find("A")>=0
     51        w = image.get_width()
     52        h = image.get_height()
     53        if quality==100:
     54            #webp lossless is unbearibly slow for only marginal compression improvements,
     55            #so force max speed:
     56            pass
     57            #speed = 100
     58        else:
     59            #normalize speed for webp: avoid low speeds!
     60            speed = int(sqrt(speed) * 10)
     61        speed = max(0, min(100, speed))
     62        pixels = image.get_pixels()
     63        assert pixels, "failed to get pixels from %s" % image
     64        Bpp = len(pixel_format)
     65        cdata = enc_webp.compress(pixels, w, h, stride=stride//Bpp, quality=quality, speed=speed, has_alpha=alpha, Bpp=Bpp)
     66        client_options = {
     67            "speed"       : speed,
     68            "rgb_format"  : pixel_format,
     69            }
     70        if quality>=0 and quality<=100:
     71            client_options["quality"] = quality
     72        if alpha:
     73            client_options["has_alpha"] = True
     74        return "webp", compression.Compressed("webp", cdata), client_options, image.get_width(), image.get_height(), 0, 24
     75    #fallback to PIL
     76    enc_pillow = get_codec("enc_pillow")
     77    if enc_pillow:
     78        log.info("using PIL fallback for webp: enc_webp=%s, stride=%s, pixel format=%s", enc_webp, stride, image.get_pixel_format())
     79        for x in ("webp", "png"):
     80            if x in enc_pillow.get_encodings():
     81                return enc_pillow.encode(x, image, quality, speed, supports_transparency)
     82    raise Exception("BUG: cannot use 'webp' encoding and none of the PIL fallbacks are available!")
     83
     84
    3185def rgb_encode(coding, image, rgb_formats, supports_transparency, speed, rgb_zlib=True, rgb_lz4=True, rgb_lzo=False):
    3286    pixel_format = strtobytes(image.get_pixel_format())
    3387    #log("rgb_encode%s pixel_format=%s, rgb_formats=%s", (coding, image, rgb_formats, supports_transparency, speed, rgb_zlib, rgb_lz4), pixel_format, rgb_formats)
  • xpra/server/window/window_source.py

     
    6464from xpra.server.cystats import time_weighted_average, logp #@UnresolvedImport
    6565from xpra.server.window.region import rectangle, add_rectangle, remove_rectangle, merge_all   #@UnresolvedImport
    6666from xpra.codecs.xor.cyxor import xor_str           #@UnresolvedImport
    67 from xpra.server.picture_encode import rgb_encode, mmap_send, argb_swap
     67from xpra.server.picture_encode import rgb_encode, webp_encode, mmap_send, argb_swap
    6868from xpra.codecs.loader import PREFERED_ENCODING_ORDER, get_codec
    6969from xpra.codecs.codec_constants import LOSSY_PIXEL_FORMATS
    7070from xpra.net import compression
     
    213213        self._encoding_quality = deque(maxlen=100)   #keep track of the target encoding_quality: (event time, info, encoding speed)
    214214        self._encoding_speed = deque(maxlen=100)     #keep track of the target encoding_speed: (event time, info, encoding speed)
    215215        # they may have fixed values:
     216        log.warn("default_encoding_options=%s", default_encoding_options)
    216217        self._fixed_quality = default_encoding_options.get("quality", 0)
    217218        self._fixed_min_quality = default_encoding_options.get("min-quality", 0)
    218219        self._fixed_speed = default_encoding_options.get("speed", 0)
     
    244245            for x in self.enc_pillow.get_encodings():
    245246                if x in self.server_core_encodings:
    246247                    self._encoders[x] = self.pillow_encode
    247         #prefer this one over PIL supplied version:
     248        #prefer these over PIL supplied version:
     249        if "webp" in self.server_core_encodings:
     250            self._encoders["webp"] = self.webp_encode
    248251        self.enc_jpeg = get_codec("enc_jpeg")
    249252        if self.enc_jpeg:
    250253            self._encoders["jpeg"] = self.jpeg_encode
     
    749752            are = self.client_refresh_encodings
    750753        else:
    751754            #sane defaults:
    752             ropts = set(("png", "rgb24", "rgb32"))          #default encodings for auto-refresh
    753             if AUTO_REFRESH_QUALITY<100 and self.image_depth>16:
    754                 ropts.add("jpeg")
     755            #ropts = set(("png", "rgb24", "rgb32"))          #default encodings for auto-refresh
     756            #if AUTO_REFRESH_QUALITY<100 and self.image_depth>16:
     757            #    ropts.add("jpeg")
     758            ropts = ("jpeg",)
    755759            are = [x for x in PREFERED_ENCODING_ORDER if x in ropts]
    756760        self.auto_refresh_encodings = [x for x in are if x in self.common_encodings]
    757761        log("update_encoding_selection: auto_refresh_encodings=%s", self.auto_refresh_encodings)
     
    850854            return "rgb32"
    851855        if "png" in self.common_encodings and quality>75:
    852856            return "png"
    853         for x in ("rgb32", "png", "rgb32"):
     857        for x in ("rgb32", "png", "webp", "rgb32"):
    854858            if x in self.common_encodings:
    855859                return x
    856860        return self.common_encodings[0]
     
    858862    def get_auto_encoding(self, pixel_count, ww, wh, speed, quality, *_args):
    859863        if pixel_count<self._rgb_auto_threshold:
    860864            return "rgb24"
    861         if "png" in self.common_encodings and ((quality>=80 and speed<80) or self.image_depth<=16):
    862             return "png"
     865        #if "png" in self.common_encodings and ((quality>=80 and speed<80) or self.image_depth<=16):
     866        #    return "png"
    863867        if "jpeg" in self.common_encodings:
    864868            return "jpeg"
    865869        return [x for x in self.common_encodings if x!="rgb"][0]
     
    10041008            info = {}
    10051009            speed = min(100, speed)
    10061010        self._current_speed = int(speed)
    1007         statslog("update_speed() wid=%s, info=%s, speed=%s", self.wid, info, self._current_speed)
     1011        statslog.info("update_speed() wid=%s, info=%s, speed=%s", self.wid, info, self._current_speed)
    10081012        self._encoding_speed.append((monotonic_time(), info, self._current_speed))
    10091013
    10101014    def set_min_speed(self, min_speed):
     
    10411045            info = {}
    10421046            quality = min(100, quality)
    10431047        self._current_quality = int(quality)
    1044         statslog("update_quality() wid=%i, info=%s, quality=%s", self.wid, info, self._current_quality)
     1048        statslog.info("update_quality() wid=%i, info=%s, quality=%s", self.wid, info, self._current_quality)
    10451049        self._encoding_quality.append((monotonic_time(), info, self._current_quality))
    10461050
    10471051    def set_min_quality(self, min_quality):
     
    13681372        def get_encoding(pixel_count):
    13691373            return get_best_encoding(pixel_count, ww, wh, speed, quality, coding)
    13701374
     1375        def pngcheck(actual_encoding):
     1376            if actual_encoding=="png":
     1377                log.error("send_full_window_update() %s(%i)=%s, get_best_encoding=%s, options=%s", get_encoding, ww*wh, actual_encoding, get_best_encoding, options)
     1378                import traceback
     1379                traceback.print_stack(limit=5)
     1380
    13711381        def send_full_window_update():
    13721382            actual_encoding = get_encoding(ww*wh)
     1383            pngcheck(actual_encoding)
    13731384            log("send_delayed_regions: using full window update %sx%s with %s", ww, wh, actual_encoding)
    13741385            assert actual_encoding is not None
    13751386            self.process_damage_region(damage_time, 0, 0, ww, wh, actual_encoding, options)
     
    14301441            if len(regions)>1 and exclude_region is None:
    14311442                pixel_count = sum(rect.width*rect.height for rect in regions)
    14321443                actual_encoding = get_encoding(pixel_count)
     1444                pngcheck(actual_encoding)
    14331445                log("send_delayed_regions: %s regions with %s pixels (encoding=%s, actual=%s)", len(regions), pixel_count, coding, actual_encoding)
    14341446                if pixel_count>=ww*wh or self.must_encode_full_frame(actual_encoding):
    14351447                    #use full screen dimensions:
     
    14501462        i_reg_enc = []
    14511463        for i,region in enumerate(regions):
    14521464            actual_encoding = get_encoding(region.width*region.height)
     1465            pngcheck(actual_encoding)
    14531466            if self.must_encode_full_frame(actual_encoding):
    14541467                log("send_delayed_regions: using full frame for %s encoding of %ix%i", actual_encoding, region.width, region.height)
    14551468                self.process_damage_region(damage_time, 0, 0, ww, wh, actual_encoding, options)
     
    15471560
    15481561
    15491562    def schedule_auto_refresh(self, packet, options):
    1550         if not self.can_refresh():
     1563        if not self.can_refresh() or True:
    15511564            self.cancel_refresh_timer()
    15521565            return
    15531566        encoding = packet[6]
     
    18071820            statslog("record_congestion_event(%i) %iKbps", late_pct, send_speed/1024)
    18081821            gs.congestion_send_speed.append((now, late_pct, send_speed))
    18091822            gs.last_congestion_time = now
     1823        self.delay_refresh(5)
    18101824
     1825    def delay_refresh(self, delay):
     1826        log.info("delay_refresh(%i)", delay)
     1827        #push it back:
     1828        rtt = self.refresh_target_time
     1829        if rtt:
     1830            self.refresh_target_time = rtt+delay
     1831
     1832
    18111833    def damage_packet_acked(self, damage_packet_sequence, width, height, decode_time, message):
    18121834        """
    18131835            The client is acknowledging a damage packet,
     
    18721894
    18731895            * 'mmap' will use 'mmap_encode'
    18741896            * 'jpeg' and 'png' are handled by 'pillow_encode'
     1897            * 'webp' uses 'webp_encode'
    18751898            * 'h264', 'h265', 'vp8' and 'vp9' use 'video_encode'
    18761899            * 'rgb24' and 'rgb32' use 'rgb_encode'
    18771900        """
     
    20202043        return packet
    20212044
    20222045
     2046    def webp_encode(self, coding, image, options):
     2047        q = options.get("quality") or self.get_quality(coding)
     2048        s = options.get("speed") or self.get_speed(coding)
     2049        return webp_encode(coding, image, self.rgb_formats, self.supports_transparency, q, s, options)
     2050
    20232051    def rgb_encode(self, coding, image, options):
    20242052        s = options.get("speed") or self._current_speed
    20252053        return rgb_encode(coding, image, self.rgb_formats, self.supports_transparency, s,
  • xpra/server/window/window_video_source.py

     
    366366        def nonvideo(q=quality, info=""):
    367367            s = max(0, min(100, speed))
    368368            q = max(0, min(100, q))
    369             log("nonvideo(%i, %s)", q, info)
     369            log.info("nonvideo(%i, %s)", q, info)
    370370            return self.get_best_nonvideo_encoding(pixel_count, ww, wh, s, q, self.non_video_encodings[0], self.non_video_encodings)
    371371
    372372        def lossless(reason):
     
    398398            return lossless("low pixel count")
    399399
    400400        if current_encoding!="auto" and current_encoding not in self.common_video_encodings:
     401            if current_encoding=="png":
     402                import traceback
     403                traceback.print_stack()
    401404            return nonvideo(info="%s not a supported video encoding" % current_encoding)
    402405
    403406        if cww*cwh<=MAX_NONVIDEO_PIXELS:
     
    419422            return nonvideo(quality+30, "not the video region")
    420423
    421424        lde = list(self.statistics.last_damage_events)
    422         lim = now-2
    423         pixels_last_2secs = sum(w*h for when,_,_,w,h in lde if when>lim)
    424         if pixels_last_2secs<5*videomin:
     425        lim = now-4
     426        pixels_last_4secs = sum(w*h for when,_,_,w,h in lde if when>lim)
     427        if pixels_last_4secs<3*videomin:
    425428            #less than 5 full frames in last 2 seconds
    426429            return nonvideo(quality+30, "not enough frames")
    427         lim = now-0.5
    428         pixels_last_05secs = sum(w*h for when,_,_,w,h in lde if when>lim)
    429         if pixels_last_05secs<pixels_last_2secs//8:
     430        lim = now-1
     431        pixels_last_sec = sum(w*h for when,_,_,w,h in lde if when>lim)
     432        if pixels_last_sec<pixels_last_4secs//8:
    430433            #framerate is dropping?
    431434            return nonvideo(quality+30, "framerate lowered")
    432435
     
    463466        #take into account how many pixels need to be encoded:
    464467        #more pixels means we switch to lossless more easily
    465468        lossless_q = min(100, self._lossless_threshold_base + self._lossless_threshold_pixel_boost * pixel_count / (ww*wh))
    466         if quality<lossless_q and self.image_depth>16 and "jpeg" in options:
     469        if quality<100 and self.image_depth>16 and "jpeg" in options:
    467470            #assume that we have "turbojpeg",
    468471            #which beats everything in terms of efficiency for lossy compression:
    469472            return "jpeg"
     473        if "webp" in options and pixel_count>16384:
     474            max_webp = 1024*1024 * (200-quality)/100 * speed/100
     475            if speed>30 and pixel_count<max_webp:
     476                return "webp"
    470477        #lossless options:
    471478        if speed>75 or self.image_depth>24:
    472479            if "rgb24" in options:
     
    541548        WindowSource.timer_full_refresh(self)
    542549
    543550
     551    def delay_refresh(self, delay):
     552        WindowSource.delay_refresh(self, delay)
     553        self.video_subregion.min_refresh_time = monotonic_time()+delay
     554        self.video_subregion.reschedule_refresh()
     555
     556
    544557    def get_refresh_exclude(self):
    545558        #exclude video region (if any) from lossless refresh:
    546559        return self.video_subregion.rectangle