diff --git a/src/xpra/client/ui_client_base.py b/src/xpra/client/ui_client_base.py index e90f8cb898..b2afa21cdc 100644 --- a/src/xpra/client/ui_client_base.py +++ b/src/xpra/client/ui_client_base.py @@ -337,7 +337,6 @@ def get_core_encodings(self): #we always support rgb24: core_encodings = ["rgb24"] for modules, encodings in { - ("dec_vpx", "csc_swscale") : ["vp8"], ("dec_webp",) : ["webp"], ("PIL",) : ["png", "png/L", "png/P", "jpeg"], }.items(): @@ -346,15 +345,19 @@ def get_core_encodings(self): log("get_core_encodings() not adding %s because of missing modules: %s", encodings, missing) continue core_encodings += encodings - #special case for avcodec which may be able to decode both 'vp8' and 'h264': - #(both of which "need" swscale - until we get more clever - # and test the availibility of GL windows) - if has_codec("csc_swscale"): #or has_codec("csc_opencl"): (see window_backing_base) - avcodec_module = get_codec("dec_avcodec") - if avcodec_module: - encodings = avcodec_module.get_codecs() - log("avcodec supports %s", encodings) - core_encodings += encodings + #special case for "dec_avcodec" which may be able to decode both 'vp8' and 'h264': + #and for "dec_vpx" which may be able to decode both 'vp8' and 'vp9': + #(both may "need" some way of converting YUV data to RGB - at least until we get more clever + # and test the availibility of GL windows... but those aren't always applicable.. + # or test if the codec can somehow gives us plain RGB out) + if has_codec("csc_swscale"): # or has_codec("csc_opencl"): (see window_backing_base) + for module in ("dec_avcodec", "dec_avcodec2", "dec_vpx"): + decoder = get_codec(module) + if decoder: + for encoding in decoder.get_encodings(): + log.info("%s supports %s", module, encoding) + if encoding not in core_encodings: + core_encodings.append(encoding) log("get_core_encodings()=%s", core_encodings) #remove duplicates and use prefered encoding order: return [x for x in PREFERED_ENCODING_ORDER if x in set(core_encodings)] diff --git a/src/xpra/client/window_backing_base.py b/src/xpra/client/window_backing_base.py index 13c0d70b71..ed6376cfed 100644 --- a/src/xpra/client/window_backing_base.py +++ b/src/xpra/client/window_backing_base.py @@ -17,7 +17,7 @@ from xpra.net.protocol import has_lz4, LZ4_uncompress from xpra.os_util import BytesIOClass, bytestostr from xpra.codecs.codec_constants import get_colorspace_from_avutil_enum -from xpra.codecs.loader import get_codec, has_codec +from xpra.codecs.loader import get_codec #logging in the draw path is expensive: @@ -54,17 +54,17 @@ def load_csc_options(): except: log.warn("failed to load csc module %s", csc_module, exc_info=True) -VPX_DECODER = None -def load_vpx_decoder(): - global VPX_DECODER - if has_codec("dec_vpx"): - VPX_DECODER = "dec_vpx" - else: - #try dec_avcodec: - avcodec_module = get_codec("dec_avcodec") - if avcodec_module and "vpx" in avcodec_module.get_codecs(): - VPX_DECODER = "dec_avcodec" - +VPX_DECODERS = {} +def load_vpx_decoders(): + global VPX_DECODERS + for codec in ("vp8", "vp9"): + #prefer native vpx ahead of avcodec: + for module in ("dec_vpx", "dec_avcodec"): + decoder = get_codec(module) + if codec in decoder.get_encodings(): + VPX_DECODERS[codec] = module + break + log("vpx decoders: %s", VPX_DECODERS) def fire_paint_callbacks(callbacks, success): for x in callbacks: @@ -82,7 +82,7 @@ def fire_paint_callbacks(callbacks, success): class WindowBackingBase(object): def __init__(self, wid, idle_add): load_csc_options() - load_vpx_decoder() + load_vpx_decoders() self.wid = wid self.idle_add = idle_add self._has_alpha = False @@ -373,8 +373,8 @@ def paint_with_video_decoder(self, decoder_name, coding, img_data, x, y, width, 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)) + raise Exception("paint_with_video_decoder: wid=%s, %s decompression error on %s bytes of picture data for %sx%s pixels using %s, options=%s" % ( + self.wid, coding, len(img_data), width, height, self._video_decoder, options)) self.do_video_paint(img, x, y, enc_width, enc_height, width, height, options, callbacks) finally: self._decoder_lock.release() @@ -472,9 +472,9 @@ def draw_region(self, x, y, width, height, coding, img_data, rowstride, options, self.paint_rgb32(img_data, x, y, width, height, rowstride, options, callbacks) elif coding=="h264": self.paint_with_video_decoder("dec_avcodec", "h264", img_data, x, y, width, height, options, callbacks) - elif coding=="vp8": - assert VPX_DECODER, "no vpx decoder available" - self.paint_with_video_decoder(VPX_DECODER, "vp8", img_data, x, y, width, height, options, callbacks) + elif coding in ("vp8", "vp9"): + assert coding in VPX_DECODERS, "no %s decoder available" % coding + self.paint_with_video_decoder(VPX_DECODERS.get(coding), coding, img_data, x, y, width, height, options, callbacks) elif coding == "webp": self.paint_webp(img_data, x, y, width, height, options, callbacks) elif coding[:3]=="png" or coding=="jpeg": diff --git a/src/xpra/codecs/dec_avcodec/decoder.pyx b/src/xpra/codecs/dec_avcodec/decoder.pyx index 95447c96a1..a873ba7f3d 100644 --- a/src/xpra/codecs/dec_avcodec/decoder.pyx +++ b/src/xpra/codecs/dec_avcodec/decoder.pyx @@ -90,6 +90,7 @@ cdef extern from "libavcodec/avcodec.h": AVPixelFormat PIX_FMT_NONE AVCodecID CODEC_ID_H264 AVCodecID CODEC_ID_VP8 + #AVCodecID CODEC_ID_VP9 #init and free: void avcodec_register_all() @@ -160,17 +161,19 @@ def get_colorspaces(): init_colorspaces() return COLORSPACES -_CODECS = None -def get_codecs(): - global _CODECS - if _CODECS is None: +CODECS = None +def get_encodings(): + global CODECS + if CODECS is None: avcodec_register_all() - _CODECS = [] + CODECS = [] if avcodec_find_decoder(CODEC_ID_H264)!=NULL: - _CODECS.append("h264") + CODECS.append("h264") if avcodec_find_decoder(CODEC_ID_VP8)!=NULL: - _CODECS.append("vp8") - return _CODECS + CODECS.append("vp8") + #if avcodec_find_decoder(CODEC_ID_VP9)!=NULL: + # CODECS.append("vp9") + return CODECS #maps AVCodecContext to the Decoder that manages it diff --git a/src/xpra/codecs/dec_avcodec2/decoder.pyx b/src/xpra/codecs/dec_avcodec2/decoder.pyx index 7e5201bba5..f819351a06 100644 --- a/src/xpra/codecs/dec_avcodec2/decoder.pyx +++ b/src/xpra/codecs/dec_avcodec2/decoder.pyx @@ -87,6 +87,7 @@ cdef extern from "libavcodec/avcodec.h": AVPixelFormat PIX_FMT_NONE AVCodecID CODEC_ID_H264 AVCodecID CODEC_ID_VP8 + #AVCodecID CODEC_ID_VP9 #init and free: void avcodec_register_all() @@ -149,17 +150,19 @@ def get_colorspaces(): init_colorspaces() return COLORSPACES -_CODECS = None -def get_codecs(): - global _CODECS - if _CODECS is None: +CODECS = None +def get_encodings(): + global CODECS + if CODECS is None: avcodec_register_all() - _CODECS = [] + CODECS = [] if avcodec_find_decoder(CODEC_ID_H264)!=NULL: - _CODECS.append("h264") + CODECS.append("h264") if avcodec_find_decoder(CODEC_ID_VP8)!=NULL: - _CODECS.append("vp8") - return _CODECS + CODECS.append("vp8") + #if avcodec_find_decoder(CODEC_ID_VP9)!=NULL: + # CODECS.append("vp9") + return CODECS cdef void clear_frame(AVFrame *frame): diff --git a/src/xpra/codecs/vpx/decoder.pyx b/src/xpra/codecs/vpx/decoder.pyx index 5d6f00ef22..4f6ea30ba6 100644 --- a/src/xpra/codecs/vpx/decoder.pyx +++ b/src/xpra/codecs/vpx/decoder.pyx @@ -12,6 +12,10 @@ log = Logger() debug = debug_if_env(log, "XPRA_VPX_DEBUG") error = log.error +DEF ENABLE_VP8 = True +DEF ENABLE_VP9 = False + + from libc.stdint cimport int64_t @@ -58,7 +62,10 @@ cdef extern from "vpx/vpx_image.h": unsigned int y_chroma_shift cdef extern from "vpx/vp8dx.h": - vpx_codec_iface_t *vpx_codec_vp8_dx() + IF ENABLE_VP8 == True: + const vpx_codec_iface_t *vpx_codec_vp8_dx() + IF ENABLE_VP9 == True: + const vpx_codec_iface_t *vpx_codec_vp9_dx() cdef extern from "vpx/vpx_decoder.h": ctypedef struct vpx_codec_enc_cfg_t: @@ -87,8 +94,26 @@ def get_version(): return vpx_codec_version_str() def get_type(self): - return "vp8" + return "vpx" + + +CODECS = [] +IF ENABLE_VP8 == True: + CODECS.append("vp8") +IF ENABLE_VP9 == True: + CODECS.append("vp9") +cdef const vpx_codec_iface_t *make_codec_dx(encoding): + IF ENABLE_VP8 == True: + if encoding=="vp8": + return vpx_codec_vp8_dx() + IF ENABLE_VP9 == True: + if encoding=="vp9": + return vpx_codec_vp9_dx() + raise Exception("unsupported encoding: %s" % encoding) + +def get_encodings(): + return CODECS #https://groups.google.com/a/webmproject.org/forum/?fromgroups#!msg/webm-discuss/f5Rmi-Cu63k/IXIzwVoXt_wJ #"RGB is not supported. You need to convert your source to YUV, and then compress that." @@ -100,7 +125,7 @@ def get_spec(colorspace): assert colorspace in COLORSPACES, "invalid colorspace: %s (must be one of %s)" % (colorspace, COLORSPACES) #quality: we only handle YUV420P but this is already accounted for by get_colorspaces() based score calculations #setup cost is reasonable (usually about 5ms) - return codec_spec(Decoder, codec_type="vp8", setup_cost=40) + return codec_spec(Decoder, codec_type="vpx", setup_cost=40) cdef vpx_img_fmt_t get_vpx_colorspace(colorspace): assert colorspace in COLORSPACES @@ -140,15 +165,17 @@ cdef class Decoder: cdef int width cdef int height cdef vpx_img_fmt_t pixfmt - cdef char* src_format + cdef char* dst_format + cdef object encoding def init_context(self, encoding, width, height, colorspace): - cdef const vpx_codec_iface_t *codec_iface = vpx_codec_vp8_dx() - cdef int flags = 0 - assert encoding=="vp8" + assert encoding in CODECS assert colorspace=="YUV420P" - self.src_format = "YUV420P" - self.pixfmt = get_vpx_colorspace(self.src_format) + cdef int flags = 0 + cdef const vpx_codec_iface_t *codec_iface = make_codec_dx(encoding) + self.encoding = encoding + self.dst_format = "YUV420P" + self.pixfmt = get_vpx_colorspace(self.dst_format) self.width = width self.height = height self.context = xmemalign(sizeof(vpx_codec_ctx_t)) @@ -166,7 +193,7 @@ cdef class Decoder: } def get_colorspace(self): - return self.src_format + return self.dst_format def get_width(self): return self.width @@ -178,7 +205,7 @@ cdef class Decoder: return self.context==NULL def get_encoding(self): - return "vp8" + return self.encoding def get_type(self): #@DuplicatedSignature return "vpx" @@ -237,4 +264,5 @@ cdef class Decoder: pixels.append(plane) image.add_buffer( padded_buf) + #log("vpx returning decoded %s image %s with colorspace=%s", self.encoding, image, image.get_pixel_format()) return image diff --git a/src/xpra/codecs/vpx/encoder.pyx b/src/xpra/codecs/vpx/encoder.pyx index 8899106188..da17e741ef 100644 --- a/src/xpra/codecs/vpx/encoder.pyx +++ b/src/xpra/codecs/vpx/encoder.pyx @@ -11,6 +11,10 @@ log = Logger() debug = debug_if_env(log, "XPRA_VPX_DEBUG") error = log.error +DEF ENABLE_VP8 = True +DEF ENABLE_VP9 = False + + from libc.stdint cimport int64_t @@ -55,7 +59,10 @@ cdef extern from "vpx/vpx_image.h": unsigned int y_chroma_shift cdef extern from "vpx/vp8cx.h": - const vpx_codec_iface_t *vpx_codec_vp8_cx() + IF ENABLE_VP8 == True: + const vpx_codec_iface_t *vpx_codec_vp8_cx() + IF ENABLE_VP9 == True: + const vpx_codec_iface_t *vpx_codec_vp9_cx() cdef extern from "vpx/vpx_encoder.h": int VPX_ENCODER_ABI_VERSION @@ -102,8 +109,24 @@ def get_version(): def get_type(): return "vpx" +CODECS = [] +IF ENABLE_VP8 == True: + CODECS.append("vp8") +IF ENABLE_VP9 == True: + CODECS.append("vp9") + def get_encodings(): - return ["vp8"] + return CODECS + + +cdef const vpx_codec_iface_t *make_codec_cx(encoding): + IF ENABLE_VP8 == True: + if encoding=="vp8": + return vpx_codec_vp8_cx() + IF ENABLE_VP9 == True: + if encoding=="vp9": + return vpx_codec_vp9_cx() + raise Exception("unsupported encoding: %s" % encoding) #https://groups.google.com/a/webmproject.org/forum/?fromgroups#!msg/webm-discuss/f5Rmi-Cu63k/IXIzwVoXt_wJ @@ -113,7 +136,7 @@ def get_colorspaces(): return COLORSPACES def get_spec(encoding, colorspace): - assert encoding in get_encodings(), "invalid encoding: %s (must be one of %s" % (encoding, get_encodings()) + assert encoding in CODECS, "invalid encoding: %s (must be one of %s" % (encoding, get_encodings()) assert colorspace in COLORSPACES, "invalid colorspace: %s (must be one of %s)" % (colorspace, COLORSPACES) #quality: we only handle YUV420P but this is already accounted for by get_colorspaces() based score calculations #setup cost is reasonable (usually about 5ms) @@ -138,12 +161,14 @@ cdef class Encoder: cdef int width cdef int height cdef int max_threads + cdef object encoding cdef char* src_format def init_context(self, int width, int height, src_format, encoding, int quality, int speed, scaling, options): #@DuplicatedSignature - assert encoding=="vp8", "invalid encoding: %s" % encoding - assert scaling==(1,1), "vp8 does not handle scaling" - cdef const vpx_codec_iface_t *codec_iface + assert encoding in CODECS, "invalid encoding: %s" % encoding + assert scaling==(1,1), "vpx does not handle scaling" + cdef const vpx_codec_iface_t *codec_iface = make_codec_cx(encoding) + self.encoding = encoding self.width = width self.height = height self.frames = 0 @@ -156,7 +181,6 @@ cdef class Encoder: log.warn("error parsing number of threads: %s", e) self.max_threads =2 - codec_iface = vpx_codec_vp8_cx() self.cfg = xmemalign(sizeof(vpx_codec_enc_cfg_t)) if self.cfg==NULL: raise Exception("failed to allocate memory for vpx encoder config") @@ -191,7 +215,7 @@ cdef class Encoder: "max_threads": self.max_threads} def get_encoding(self): - return "vp8" + return self.encoding def get_width(self): return self.width @@ -279,6 +303,7 @@ cdef class Encoder: cout = get_frame_buffer(pkt) img = cout[:coutsz] free(image) + #log("vpx returning %s image: %s bytes", self.encoding, len(img)) return img def set_encoding_speed(self, int pct): diff --git a/src/xpra/server/window_video_source.py b/src/xpra/server/window_video_source.py index c093efe9a8..20a57eb48c 100644 --- a/src/xpra/server/window_video_source.py +++ b/src/xpra/server/window_video_source.py @@ -69,7 +69,7 @@ def __init__(self, *args): #0.10 onwards should have specified csc_modes: self.csc_modes = self.encoding_options.get("csc_modes", def_csc_modes) - self.video_encodings = ("vp8", "h264") + self.video_encodings = ("vp8", "vp9", "h264") for x in self.video_encodings: if x in self.server_core_encodings: self._encoders[x] = self.video_encode