From 509af1a4cf7124076a6e9693742561fd435fc2c6 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 17 Apr 2024 22:36:42 +0700 Subject: [PATCH] #3964 automatically switch to GStreamer 'stream' mode if that's the only type of GPU accelerated encoder available and the window is one of the 'STREAM_CONTENT_TYPES', which defaults to 'desktop and 'video' --- fs/etc/xpra/conf.d/30_picture.conf.in | 2 +- xpra/codecs/gstreamer/capture.py | 1 + xpra/codecs/video.py | 16 +++++----- xpra/scripts/config.py | 2 +- xpra/server/window/video_compress.py | 46 +++++++++++++++++++-------- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/fs/etc/xpra/conf.d/30_picture.conf.in b/fs/etc/xpra/conf.d/30_picture.conf.in index 59a5fa1a16..0c15cca2e0 100644 --- a/fs/etc/xpra/conf.d/30_picture.conf.in +++ b/fs/etc/xpra/conf.d/30_picture.conf.in @@ -23,7 +23,7 @@ encoding = auto # video-encoders = none # video-encoders = all # video-encoders = all,-x264 -video-encoders = all,-gstreamer +video-encoders = all # Used by both the client and server for colourspace conversion: # csc-modules = swscale, cython, libyuv diff --git a/xpra/codecs/gstreamer/capture.py b/xpra/codecs/gstreamer/capture.py index 8e8e7ad623..a52b41b816 100755 --- a/xpra/codecs/gstreamer/capture.py +++ b/xpra/codecs/gstreamer/capture.py @@ -192,6 +192,7 @@ def create_pipeline(self, capture_element: str = "ximagesrc") -> None: encoder = choose_encoder(elements) if not encoder: raise RuntimeError(f"no encoders found for {encoding!r}") + self.encoder = encoder options = typedict({ "speed": 100, "quality": 100, diff --git a/xpra/codecs/video.py b/xpra/codecs/video.py index 28a8b8db38..aa93cceb49 100755 --- a/xpra/codecs/video.py +++ b/xpra/codecs/video.py @@ -264,9 +264,9 @@ def get_info(self) -> dict[str, Any]: for x in ALL_CSC_MODULE_OPTIONS: cscm[x] = modstatus(get_csc_module_name(x), get_csc_modules(), self.csc_modules) d["gpu"] = { - "encodings": self.get_gpu_encodings(), - "csc": self.get_gpu_csc(), - "decodings": self.get_gpu_decodings(), + "encodings": tuple(self.get_gpu_encodings().keys()), + "csc": tuple(self.get_gpu_csc().keys()), + "decodings": tuple(self.get_gpu_decodings().keys()), } return d @@ -284,7 +284,7 @@ def init(self) -> None: self._initialized = True log("VideoHelper.init() done") - def get_gpu_options(self, codec_specs: Vdict) -> dict[str, list[str]]: + def get_gpu_options(self, codec_specs: Vdict) -> dict[str, list[CodecSpec]]: log(f"get_gpu_options({codec_specs})") gpu_fmts: dict[str, list[str]] = {} for in_fmt, vdict in codec_specs.items(): @@ -292,16 +292,16 @@ def get_gpu_options(self, codec_specs: Vdict) -> dict[str, list[str]]: log(f"get_gpu_options {out_fmt}: {codecs}") for codec in codecs: if codec.gpu_cost > codec.cpu_cost: - gpu_fmts.setdefault(in_fmt, []).append(codec.codec_type) + gpu_fmts.setdefault(in_fmt, []).append(codec) return gpu_fmts - def get_gpu_encodings(self) -> dict[str, list[str]]: + def get_gpu_encodings(self) -> dict[str, list[CodecSpec]]: return self.get_gpu_options(self._video_encoder_specs) - def get_gpu_csc(self) -> dict[str, list[str]]: + def get_gpu_csc(self) -> dict[str, list[CodecSpec]]: return self.get_gpu_options(self._csc_encoder_specs) - def get_gpu_decodings(self) -> dict[str, list[str]]: + def get_gpu_decodings(self) -> dict[str, list[CodecSpec]]: return self.get_gpu_options(self._video_decoder_specs) def get_encodings(self) -> tuple[str, ...]: diff --git a/xpra/scripts/config.py b/xpra/scripts/config.py index 855c6e75f5..b1b5906918 100755 --- a/xpra/scripts/config.py +++ b/xpra/scripts/config.py @@ -1182,7 +1182,7 @@ def get_defaults() -> dict[str, Any]: "remote-xpra" : get_remote_run_xpra_scripts(), "encodings" : ["all"], "proxy-video-encoders" : ["none"], - "video-encoders" : ["all", "-gstreamer"], + "video-encoders" : ["all"], "csc-modules" : ["all"], "video-decoders" : ["all"], "speaker-codec" : [], diff --git a/xpra/server/window/video_compress.py b/xpra/server/window/video_compress.py index a68ef9641a..0d842d0409 100644 --- a/xpra/server/window/video_compress.py +++ b/xpra/server/window/video_compress.py @@ -106,7 +106,8 @@ def parse_scaling_options_str(scaling_options_str: str) -> tuple: SCROLL_MIN_PERCENT = max(1, min(100, envint("XPRA_SCROLL_MIN_PERCENT", 30))) MIN_SCROLL_IMAGE_SIZE = envint("XPRA_MIN_SCROLL_IMAGE_SIZE", 128) -STREAM_MODE = os.environ.get("XPRA_STREAM_MODE", "") +STREAM_MODE = os.environ.get("XPRA_STREAM_MODE", "auto") +STREAM_CONTENT_TYPES = os.environ.get("XPRA_STREAM_CONTENT_TYPES", "desktop,video").split(",") GSTREAMER_X11_TIMEOUT = envint("XPRA_GSTREAMER_X11_TIMEOUT", 500) SAVE_VIDEO_PATH = os.environ.get("XPRA_SAVE_VIDEO_PATH", "") @@ -207,6 +208,7 @@ def init_vars(self) -> None: self.encode_from_queue_due = 0 self.scroll_data = None self.last_scroll_time = 0 + self.stream_mode = STREAM_MODE self.gstreamer_pipeline = None self._csc_encoder = None @@ -295,6 +297,7 @@ def get_info(self) -> dict[str,Any]: info["video_subregion"] = sri info["scaling"] = self.actual_scaling info["video-max-size"] = self.video_max_size + info["stream-mode"] = self.stream_mode def addcinfo(prefix, x): if not x: @@ -446,19 +449,31 @@ def update_encoding_selection(self, encoding="", exclude=None, init=False) -> No log(*msg_args) log(" csc modes=%", self.full_csc_modes) self.common_video_encodings = preforder(set(self.video_encodings) & set(self.core_encodings)) - log("update_encoding_selection: common_video_encodings=%s, csc_encoder=%s, video_encoder=%s", - self.common_video_encodings, self._csc_encoder, self._video_encoder) + videolog("update_encoding_selection: common_video_encodings=%s, csc_encoder=%s, video_encoder=%s", + self.common_video_encodings, self._csc_encoder, self._video_encoder) if encoding in ("stream", "auto", "grayscale"): vh = self.video_helper - if encoding == "auto" and self.content_type in ("desktop", "video") and vh: + if encoding == "auto" and self.content_type in STREAM_CONTENT_TYPES and vh: accel = vh.get_gpu_encodings() common_accel = preforder(set(self.common_video_encodings) & set(accel.keys())) - log(f"gpu {accel=} - {common_accel=}") + videolog(f"gpu {accel=} - {common_accel=}") if common_accel: encoding = "stream" + accel_types: set[str] = set() + for gpu_encoding in common_accel: + for accel_option in accel.get(gpu_encoding, ()): + # 'gstreamer-vah264lpenc' -> 'gstreamer' + accel_types.add(accel_option.codec_type.split("-", 1)[0]) + videolog(f"gpu encoder types: {accel_types}") + self.stream_mode = STREAM_MODE + # switch to GStreamer mode if all the GPU accelerated options require it: + if self.stream_mode == "auto" and len(accel_types) == 1 and tuple(accel_types)[0] == "gstreamer": + self.stream_mode = "gstreamer" if first_time(f"gpu-stream-{self.wid}"): - log.info(f"found gpu accelerated encodings: {csv(common_accel)}") - log.info(f"switching to {encoding!r} encoding for {self.content_type!r} content type") + videolog.info(f"found GPU accelerated encoders for: {csv(common_accel)}") + videolog.info(f"switching to {encoding!r} encoding for {self.content_type!r} window {self.wid}") + if self.stream_mode == "gstreamer": + videolog.info("using 'gstreamer' stream mode") super().update_encoding_selection(encoding, exclude, init) self.supports_scrolling = "scroll" in self.common_encodings @@ -631,7 +646,7 @@ def get_best_nonvideo_encoding(self, ww: int, wh: int, options : dict, return super().do_get_auto_encoding(ww, wh, options, current_encoding or self.encoding, encoding_options) def do_damage(self, ww: int, wh: int, x: int, y: int, w: int, h: int, options) -> None: - if ww >= 64 and wh >= 64 and self.encoding == "stream" and STREAM_MODE == "gstreamer" and self.common_video_encodings: # noqa: E501 + if ww >= 64 and wh >= 64 and self.encoding == "stream" and self.stream_mode == "gstreamer": # in this mode, we start a pipeline once # and let it submit packets, bypassing all the usual logic: if self.gstreamer_pipeline or self.start_gstreamer_pipeline(): @@ -697,13 +712,16 @@ def new_gstreamer_frame(self, _capture_pipeline, coding: str, data, client_info: gstlog(f"new_gstreamer_frame: {coding}") if not self.window.is_managed(): return - - # ensures that more damage events will be emitted: - def continue_damage(): - self.ui_thread_check() - self.window.acknowledge_changes() + gp = self.gstreamer_pipeline + if gp and (LOG_ENCODERS or compresslog.is_debug_enabled()): + client_info["encoder"] = gp.encoder self.direct_queue_draw(coding, data, client_info) - self.idle_add(continue_damage) + self.idle_add(self.gstreamer_continue_damage) + + def gstreamer_continue_damage(self) -> None: + # ensures that more damage events will be emitted + self.ui_thread_check() + self.window.acknowledge_changes() def update_window_dimensions(self, ww, wh): super().update_window_dimensions(ww, wh)