Skip to content

Commit

Permalink
#3964 automatically switch to GStreamer 'stream' mode
Browse files Browse the repository at this point in the history
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'
  • Loading branch information
totaam committed Apr 17, 2024
1 parent 2c7452a commit 509af1a
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 24 deletions.
2 changes: 1 addition & 1 deletion fs/etc/xpra/conf.d/30_picture.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions xpra/codecs/gstreamer/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 8 additions & 8 deletions xpra/codecs/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -284,24 +284,24 @@ 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():
for out_fmt, codecs in vdict.items():
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, ...]:
Expand Down
2 changes: 1 addition & 1 deletion xpra/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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" : [],
Expand Down
46 changes: 32 additions & 14 deletions xpra/server/window/video_compress.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 509af1a

Please sign in to comment.