frontends (tools/webrtc): add a options to merge video for PiP and to specify size:

- the `merge_pip` option is now used to indicate if local feedback and remote video
  streams must be merged with local feedback being a PiP of remote video. By default, it's
  done for `SINKS_AUTO`, but it can be manually specified to use it with `SINKS_APP` or to
  split ``SINKS_AUTO`` in 2 windows.
- target size of the compositor used with `merge_pip` can be specified. By default, 720p
  is used.

rel 428
This commit is contained in:
Goffi 2024-02-16 18:46:02 +01:00
commit ec3e037a1a

View file

@ -69,7 +69,7 @@ class ScreenshareError(Exception):
@dataclass
class AppSinkData:
local_video_cb: Callable
remote_video_cb: Callable
remote_video_cb: Callable|None
class DesktopPortal:
@ -255,7 +255,29 @@ class WebRTC:
sinks: str = SINKS_AUTO,
appsink_data: AppSinkData | None = None,
reset_cb: Callable | None = None,
merge_pip: bool|None = None,
target_size: tuple[int, int]|None = None,
) -> None:
"""Initializes a new WebRTC instance.
@param bridge: An instance of backend bridge.
@param profile: Libervia profile.
@param sources: Which kind of source to use.
@param sinks: Which kind of sinks to use.
@param appsink_data: configuration data for appsink (when SINKS_APP is used). Must
not be used for other sinks.
@param reset_cb: An optional Callable that is triggered on reset events. Can be
used to reset UI data on new calls.
@param merge_pip: A boolean flag indicating whether Picture-in-Picture mode is
enabled. When PiP is used, local feedback is merged to remote video stream.
Only one video stream is then produced (the local one).
If None, PiP mode is selected automatically according to selected sink (it's
used for SINKS_AUTO only for now).
@param target_size: Expected size of the final sink stream. Mainly use by composer
when ``merge_pip`` is set.
None to autodetect (not real autodetection implemeted yet, default to
(1280,720)).
"""
self.main_loop = asyncio.get_event_loop()
self.bridge = bridge
self.profile = profile
@ -266,7 +288,19 @@ class WebRTC:
self.desktop_sharing_data = None
self.sources = sources
self.sinks = sinks
if target_size is None:
target_size=(1280, 720)
self.target_width, self.target_height = target_size
if merge_pip is None:
merge_pip = sinks == SINKS_AUTO
self.merge_pip = merge_pip
if sinks == SINKS_APP:
if (
merge_pip
and appsink_data is not None
and appsink_data.remote_video_cb is not None
):
raise ValueError("Remote_video_cb can't be used when merge_pip is used!")
self.appsink_data = appsink_data
elif appsink_data is not None:
raise exceptions.InternalError(
@ -576,7 +610,6 @@ class WebRTC:
else:
raise exceptions.InternalError(f'Unknown "sources" value: {self.sources!r}')
extra_elt = ""
if self.sinks == SINKS_APP:
local_video_sink_elt = (
@ -584,11 +617,21 @@ class WebRTC:
"sync=True"
)
elif self.sinks == SINKS_AUTO:
extra_elt = "compositor name=compositor ! autovideosink"
local_video_sink_elt = """compositor.sink_1"""
local_video_sink_elt = "autovideosink"
else:
raise exceptions.InternalError(f"Unknown sinks value {self.sinks!r}")
if self.merge_pip:
extra_elt = (
"compositor name=compositor background=black "
f"! video/x-raw,width={self.target_width},height={self.target_height},"
"framerate=30/1 "
f"! {local_video_sink_elt}"
)
local_video_sink_elt = "compositor.sink_1"
else:
extra_elt = ""
self.gst_pipe_desc = f"""
webrtcbin latency=100 name=sendrecv bundle-policy=max-bundle
@ -629,7 +672,11 @@ class WebRTC:
log.debug(f"Gstreamer pipeline: {self.gst_pipe_desc}")
# Create the pipeline
self.pipeline = Gst.parse_launch(self.gst_pipe_desc)
try:
self.pipeline = Gst.parse_launch(self.gst_pipe_desc)
except Exception:
log.exception("Can't parse pipeline")
self.pipeline = None
if not self.pipeline:
raise exceptions.InternalError("Failed to create Gstreamer pipeline.")
@ -819,8 +866,8 @@ class WebRTC:
self._remote_video_pad = pad
# Check and log the original size of the video
width = s.get_int("width").value
height = s.get_int("height").value
width = self.target_width
height = self.target_height
log.info(f"Original video size: {width}x{height}")
# This is a fix for an issue found with Movim on desktop: a non standard
@ -834,20 +881,8 @@ class WebRTC:
log.info(f"Adjusted video size: {width}x{height}")
conv = Gst.ElementFactory.make("videoconvert")
if self.sinks == SINKS_APP:
assert self.appsink_data is not None
remote_video_sink = Gst.ElementFactory.make("appsink")
appsink_caps = Gst.Caps.from_string("video/x-raw,format=RGB")
remote_video_sink.set_property("caps", appsink_caps)
remote_video_sink.set_property("emit-signals", True)
remote_video_sink.set_property("drop", True)
remote_video_sink.set_property("max-buffers", 1)
remote_video_sink.set_property("sync", True)
remote_video_sink.connect("new-sample", self.appsink_data.remote_video_cb)
self.pipeline.add(remote_video_sink)
elif self.sinks == SINKS_AUTO:
if self.merge_pip:
# with ``merge_pip`` set, we plug the remote stream to the composer
compositor = self.pipeline.get_by_name("compositor")
sink1_pad = compositor.get_static_pad("sink_1")
@ -863,13 +898,36 @@ class WebRTC:
sink1_pad.set_property("ypos", height - local_height)
sink1_pad.set_property("width", local_width)
sink1_pad.set_property("height", local_height)
sink1_pad.set_property("sizing-policy", 1)
sink1_pad.set_property("zorder", 1)
# Request a new pad for the remote stream
sink_pad_template = compositor.get_pad_template("sink_%u")
remote_video_sink = compositor.request_pad(sink_pad_template, None, None)
remote_video_sink.set_property("zorder", 0)
remote_video_sink.set_property("width", width)
remote_video_sink.set_property("height", height)
remote_video_sink.set_property("sizing-policy", 1)
elif self.sinks == SINKS_APP:
# ``app`` sink without ``self.merge_pip`` set, be create the sink and
# connect it to the ``remote_video_cb``.
assert self.appsink_data is not None
remote_video_sink = Gst.ElementFactory.make("appsink")
remote_video_caps = Gst.Caps.from_string("video/x-raw,format=RGB")
remote_video_sink.set_property("caps", remote_video_caps)
remote_video_sink.set_property("emit-signals", True)
remote_video_sink.set_property("drop", True)
remote_video_sink.set_property("max-buffers", 1)
remote_video_sink.set_property("sync", True)
remote_video_sink.connect("new-sample", self.appsink_data.remote_video_cb)
self.pipeline.add(remote_video_sink)
elif self.sinks == SINKS_AUTO:
# if ``self.merge_pip`` is not set, we create a dedicated
# ``autovideosink`` for remote stream.
remote_video_sink = Gst.ElementFactory.make("autovideosink")
self.pipeline.add(remote_video_sink)
else:
raise exceptions.InternalError(f'Unhandled "sinks" value: {self.sinks!r}')