diff --git a/xpra/codecs/gstreamer/decoder.py b/xpra/codecs/gstreamer/decoder.py index fbcf81292c..ec2850bca6 100644 --- a/xpra/codecs/gstreamer/decoder.py +++ b/xpra/codecs/gstreamer/decoder.py @@ -9,6 +9,7 @@ from xpra.gst_common import ( GST_FLOW_OK, STREAM_TYPE, GST_FORMAT_BYTES, make_buffer, has_plugins, + get_caps_str, get_element_str, ) from xpra.codecs.gstreamer.codec_common import ( VideoPipeline, @@ -77,27 +78,37 @@ def create_pipeline(self, options): if self.encoding not in get_encodings(): raise ValueError(f"invalid encoding {self.encoding!r}") self.dst_formats = options.strtupleget("dst-formats") - gst_rgb_format = "I420" - IMAGE_CAPS = f"video/x-raw,width={self.width},height={self.height},format=(string){gst_rgb_format}" - stream_caps = f"video/x-{self.encoding},width={self.width},height={self.height}" - decode = [f"{self.encoding}dec"] - if self.encoding in ("vp8", "vp9"): - pass - elif self.encoding=="av1": + stream_attrs = { + "width" : self.width, + "height" : self.height, + } + decoder_element = f"{self.encoding}dec" + if self.encoding in ("vp8", "vp9", "av1"): pass elif self.encoding=="h264" and not WIN32: - profile = options.strget("profile", "main") - stream_caps += f",profile={profile},stream-format=avc,alignment=au" + for k,v in { + "profile" : "main", + "stream-format" : "byte-stream", + "alignment" : "au", + }.items(): + stream_attrs[k] = options.strget(k, v) #decode = ["vaapih264dec"] #decode = ["openh264dec"] - decode = [f"avdec_{self.encoding}"] + decoder_element = f"avdec_{self.encoding}" else: raise RuntimeError(f"invalid encoding {self.encoding}") + stream_caps = get_caps_str(f"video/x-{self.encoding}", stream_attrs) + gst_rgb_format = "I420" + output_caps = get_caps_str("video/x-raw", { + "width" : self.width, + "height" : self.height, + "format" : gst_rgb_format, + }) elements = [ #"do-timestamp=1", - f"appsrc name=src emit-signals=1 block=0 is-live=1 do-timestamp=1 stream-type={STREAM_TYPE} format={GST_FORMAT_BYTES} caps={stream_caps}" - ] + decode + [ - f"appsink name=sink emit-signals=true max-buffers=10 drop=true sync=false async=false qos=false caps={IMAGE_CAPS}", + f"appsrc name=src emit-signals=1 block=0 is-live=1 do-timestamp=1 stream-type={STREAM_TYPE} format={GST_FORMAT_BYTES} caps={stream_caps}", + decoder_element, + f"appsink name=sink emit-signals=true max-buffers=10 drop=true sync=false async=false qos=false caps={output_caps}", ] if not self.setup_pipeline_and_bus(elements): raise RuntimeError("failed to setup gstreamer pipeline") diff --git a/xpra/codecs/gstreamer/encoder.py b/xpra/codecs/gstreamer/encoder.py index c55de43589..1de2d169c9 100755 --- a/xpra/codecs/gstreamer/encoder.py +++ b/xpra/codecs/gstreamer/encoder.py @@ -11,6 +11,7 @@ from xpra.codecs.codec_constants import video_spec, get_profile, get_x264_quality, get_x264_preset from xpra.gst_common import ( import_gst, normv, get_all_plugin_names, + get_caps_str, get_element_str, STREAM_TYPE, BUFFER_FORMAT, GST_FLOW_OK, ) from xpra.codecs.gstreamer.codec_common import ( @@ -214,26 +215,6 @@ def get_gst_rgb_format(rgb_format : str) -> str: #"RGB8P" }[rgb_format] -def get_caps_str(ctype:str="video/x-raw", caps=None) -> str: - if not caps: - return ctype - def s(v): - if isinstance(v, str): - return f"(string){v}" - if isinstance(v, tuple): - return "/".join(str(x) for x in v) #ie: "60/1" - return str(v) - els = [ctype] - for k,v in caps.items(): - els.append(f"{k}={s(v)}") - return ",".join(els) - -def get_element_str(element:str, eopts=None): - s = element - if eopts: - s += " "+" ".join(f"{k}={v}" for k,v in eopts.items()) - return s - class Encoder(VideoPipeline): __gsignals__ = VideoPipeline.__generic_signals__.copy() @@ -250,6 +231,7 @@ def __repr__(self): def create_pipeline(self, options : typedict): if self.encoding not in get_encodings(): raise ValueError(f"invalid encoding {self.encoding!r}") + self.extra_client_info = {} self.dst_formats = options.strtupleget("dst-formats") gst_rgb_format = get_gst_rgb_format(self.colorspace) vcaps = { @@ -283,15 +265,16 @@ def get_encoder_options(self, options:typedict): s = options.intget("speed", 50) eopts.update({ "pass" : "qual", - "bframes" : int(options.boolget("b-frames", False)), + "bframes" : 0, #int(options.boolget("b-frames", False)), "quantizer" : q, "speed-preset" : get_x264_preset(s), }) - vopts.update({ + self.extra_client_info = { "profile" : profile, "alignment" : "au", "stream-format" : "byte-stream", - }) + } + vopts.update(self.extra_client_info) return eopts, vopts def get_src_format(self): @@ -312,9 +295,9 @@ def on_new_sample(self, _bus) -> int: if size: data = buf.extract_dup(0, size) #log(" output=%s", hexstr(data)) - client_info = { - "frame" : self.frames, - } + client_info = self.extra_client_info + self.extra_client_info = {} + client_info["frame"] = self.frames self.frames += 1 pts = normv(buf.pts) if pts>=0: @@ -326,6 +309,7 @@ def on_new_sample(self, _bus) -> int: if qs>0: client_info["delayed"] = qs self.frame_queue.put((data, client_info)) + log(f"added data to frame queue, client_info={client_info}") return GST_FLOW_OK diff --git a/xpra/gst_common.py b/xpra/gst_common.py index 3ccf136f9c..9e1bf6750f 100755 --- a/xpra/gst_common.py +++ b/xpra/gst_common.py @@ -100,6 +100,28 @@ def has_plugins(*names) -> bool: log("missing %s from %s", missing, names) return len(missing)==0 + +def get_caps_str(ctype:str="video/x-raw", caps=None) -> str: + if not caps: + return ctype + def s(v): + if isinstance(v, str): + return f"(string){v}" + if isinstance(v, tuple): + return "/".join(str(x) for x in v) #ie: "60/1" + return str(v) + els = [ctype] + for k,v in caps.items(): + els.append(f"{k}={s(v)}") + return ",".join(els) + +def get_element_str(element:str, eopts=None): + s = element + if eopts: + s += " "+" ".join(f"{k}={v}" for k,v in eopts.items()) + return s + + def format_element_options(options): return csv(f"{k}={v}" for k,v in options.items())