Skip to content

Commit

Permalink
Support PipeWire audio on more systems
Browse files Browse the repository at this point in the history
First, support is added for using pipewire-media-session on systems that
haven't yet been migrated to WirePlumber. While pipewire-media-session
is effectively deprecated, it is still the only session manager on
enough systems to be worth supporting.

Second, the script now calls the pipewire binary directly to launch the
PulseAudio compatibility layer instead of via the pipewire-pulse
symlink. Support for the PulseAudio protocol is built into PipeWire, but
Debian has a separate package to install the pipewire-pulse symlink,
config file, user service, and documentation. Since CRD doesn't use the
user service and provides its own config file, bypassing the symlink
allows PipeWire audio forwarding to work even on systems without the
pipewire-pulse package installed.

(cherry picked from commit b253792)

Bug: 1457635
Change-Id: I542ef3b25b9f5bd9bdf6f5dcd0c674be1d11bbd3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4641281
Auto-Submit: Erik Jensen <rkjnsn@chromium.org>
Commit-Queue: Erik Jensen <rkjnsn@chromium.org>
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Cr-Original-Commit-Position: refs/heads/main@{#1161964}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4643563
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: Jamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/branch-heads/5845@{#65}
Cr-Branched-From: 5a5dff6-refs/heads/main@{#1160321}
  • Loading branch information
Erik Jensen authored and Chromium LUCI CQ committed Jun 25, 2023
1 parent d1a46ed commit 1de9788
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 31 deletions.
1 change: 1 addition & 0 deletions remoting/host/installer/linux/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ action("remoting_me2me_host_deb_installer") {
"//remoting/host/linux/configure_url_forwarder.py",
"//remoting/host/linux/linux_me2me_host.py",
"//remoting/host/linux/pipewire.conf.template",
"//remoting/host/linux/pipewire-media-session.conf.template",
"//remoting/host/linux/pipewire-pulse.conf.template",
"//remoting/host/linux/wireplumber.conf.template",
"$root_gen_dir/remoting/CREDITS.txt",
Expand Down
3 changes: 3 additions & 0 deletions remoting/host/installer/linux/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ install:
install -m 0644 \
"$(SRC_DIR)/remoting/host/linux/pipewire.conf.template" \
"$(INSTALL_DIR)/pipewire.conf.template"
install -m 0644 \
"$(SRC_DIR)/remoting/host/linux/pipewire-media-session.conf.template" \
"$(INSTALL_DIR)/pipewire-media-session.conf.template"
install -m 0644 \
"$(SRC_DIR)/remoting/host/linux/pipewire-pulse.conf.template" \
"$(INSTALL_DIR)/pipewire-pulse.conf.template"
Expand Down
2 changes: 1 addition & 1 deletion remoting/host/installer/linux/debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Homepage: https://chrome.google.com/remotedesktop
Package: chrome-remote-desktop
Architecture: any
Depends: adduser, xvfb, xserver-xorg-video-dummy (>= 1:0.3.8-1), policykit-1, xbase-clients, psmisc, python3 (>= 3.5), python3-packaging, python3-psutil, python3-xdg (>= 0.25), ${shlibs:Depends}, ${misc:Depends}, ${python3:Depends}
Recommends: xserver-xorg-video-dummy (>= 1:0.4.0), pipewire (>= 0.3.53), pipewire-pulse, wireplumber
Recommends: xserver-xorg-video-dummy (>= 1:0.4.0), pipewire (>= 0.3.53)
Suggests: google-chrome-stable | google-chrome-beta | google-chrome-unstable
Description: Chrome Remote Desktop Beta
Chrome Remote Desktop allows you to securely access your computer over the
Expand Down
1 change: 1 addition & 0 deletions remoting/host/linux/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ if (enable_me2me_host) {

copy("remoting_me2me_host_copy_pipewire_templates") {
sources = [
"pipewire-media-session.conf.template",
"pipewire-pulse.conf.template",
"pipewire.conf.template",
"wireplumber.conf.template",
Expand Down
87 changes: 57 additions & 30 deletions remoting/host/linux/linux_me2me_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,40 +293,53 @@ def is_googler_owned(config):
return False


def is_pipewire_supported():
for binary in ["pipewire", "pipewire-pulse", "wireplumber"]:
if shutil.which(binary) is None:
logging.warning(binary
+ " not found. Not enabling PipeWire audio support.")
return False
def get_pipewire_session_manager():
"""Returns the PipeWire session manager supported on this system (either
"wireplumber" or "pipewire-media-session"), or None if a supported PipeWire
installation is not found."""

if shutil.which("pipewire") is None:
logging.warning("PipeWire not found. Not enabling PipeWire audio support.")
return None

try:
version_output = subprocess.check_output(["pipewire", "--version"],
universal_newlines=True)
except subprocess.CalledProcessError as e:
logging.warning("Failed to execute pipewire. Not enabling PipeWire audio"
+ " support: " + str(e))
return False
return None

match = re.search(r"pipewire (\S+)$", version_output, re.MULTILINE)
if not match:
logging.warning("Failed to determine pipewire version. Not enabling"
+ " PipeWire audio support.")
return False
return None

try:
pipewire_version = version.parse(match[1])
except version.InvalidVersion as e:
logging.warning("Failed to parse pipewire version. Not enabling PipeWire"
+ " audio support: " + str(e))
return False
return None

if pipewire_version < version.parse("0.3.53"):
logging.warning("Installed pipewire version is too old. Not enabling"
+ " PipeWire audio support.")
return False
return None

session_manager = None
for binary in ["wireplumber", "pipewire-media-session"]:
if shutil.which(binary) is not None:
session_manager = binary
break

return True
if session_manager is None:
logging.warning("No session manager found. Not enabling PipeWire audio"
+ " support.")
return None

return session_manager


def terminate_process(pid, name):
Expand Down Expand Up @@ -544,7 +557,8 @@ def __init__(self, sizes, server_inhibitor=None, pipewire_inhibitor=None,
self.server_proc = None
self.pipewire_proc = None
self.pipewire_pulse_proc = None
self.wireplumber_proc = None
self.pipewire_session_manager = None
self.pipewire_session_manager_proc = None
self.pre_session_proc = None
self.session_proc = None
self.host_proc = None
Expand Down Expand Up @@ -624,12 +638,13 @@ def _setup_gnubby(self):
self.child_env["SSH_AUTH_SOCK"] = self.ssh_auth_sockname

def _launch_pipewire(self, instance_name, runtime_path, sink_name):
if not is_pipewire_supported():
self.pipewire_session_manager = get_pipewire_session_manager()
if self.pipewire_session_manager is None:
return False

try:
for config_file in ["pipewire.conf", "pipewire-pulse.conf",
"wireplumber.conf"]:
self.pipewire_session_manager + ".conf"]:
with (open(os.path.join(SCRIPT_DIR, config_file + ".template"), "r")
as infile,
open(os.path.join(runtime_path, config_file), "w") as outfile):
Expand All @@ -642,20 +657,28 @@ def _launch_pipewire(self, instance_name, runtime_path, sink_name):
logging.info("Launching pipewire")
pipewire_cmd = ["pipewire", "-c",
os.path.join(runtime_path, "pipewire.conf")]
pipewire_pulse_cmd = ["pipewire-pulse", "-c",
# PulseAudio protocol support is built into PipeWire for the versions we
# support. Invoking the pipewire binary directly instead of via the
# pipewire-pulse symlink allows this to work even if the pipewire-pulse
# package is not installed (e.g., if the user is still using PulseAudio
# for local sessions).
pipewire_pulse_cmd = ["pipewire", "-c",
os.path.join(runtime_path, "pipewire-pulse.conf")]
wireplumber_cmd = ["wireplumber", "-c",
os.path.join(runtime_path, "wireplumber.conf")]
session_manager_cmd = [
self.pipewire_session_manager, "-c",
os.path.join(runtime_path, self.pipewire_session_manager + ".conf")]

# Terminate any stale processes before relaunching.
for command in [pipewire_cmd, pipewire_pulse_cmd, wireplumber_cmd]:
for command in [pipewire_cmd, pipewire_pulse_cmd, session_manager_cmd]:
terminate_command_if_running(command)

self.pipewire_proc = subprocess.Popen(pipewire_cmd, env=self.child_env)
self.pipewire_pulse_proc = subprocess.Popen(pipewire_pulse_cmd,
env=self.child_env)
self.wireplumber_proc = subprocess.Popen(wireplumber_cmd,
env=self.child_env)
# MEDIA_SESSION_CONFIG_DIR is needed to use an absolute path with
# pipewire-media-session.
self.pipewire_session_manager_proc = subprocess.Popen(session_manager_cmd,
env={**self.child_env, "MEDIA_SESSION_CONFIG_DIR": "/"})

# Directs native PipeWire clients to the correct instance
self.child_env["PIPEWIRE_REMOTE"] = instance_name
Expand All @@ -667,12 +690,13 @@ def _launch_pipewire(self, instance_name, runtime_path, sink_name):
# Clean up any processes that did start
for proc, name in [(self.pipewire_proc, "pipewire"),
(self.pipewire_pulse_proc, "pipewire-pulse"),
(self.wireplumber_proc, "wireplumber")]:
(self.pipewire_session_manager_proc,
self.pipewire_session_manager)]:
if proc is not None:
terminate_process(proc.pid, name)
self.pipewire_proc = None
self.pipewire_pulse_proc = None
self.wireplumber_proc = None
self.pipewire_session_manager_proc = None

return False

Expand Down Expand Up @@ -799,14 +823,15 @@ def cleanup(self):
(self.pre_session_proc, "pre-session"),
(self.pipewire_proc, "pipewire"),
(self.pipewire_pulse_proc, "pipewire-pulse"),
(self.wireplumber_proc, "wireplumber"),
(self.pipewire_session_manager_proc,
self.pipewire_session_manager),
(self.server_proc, "display server")]:
if proc is not None:
terminate_process(proc.pid, name)
self.server_proc = None
self.pipewire_proc = None
self.pipewire_pulse_proc = None
self.wireplumber_proc = None
self.pipewire_session_manager_proc = None
self.pre_session_proc = None
self.session_proc = None
self.host_proc = None
Expand Down Expand Up @@ -864,22 +889,24 @@ def on_process_exit(self, pid, status):
self.pipewire_pulse_proc = None
pipewire_process = True

if self.wireplumber_proc is not None and pid == self.wireplumber_proc.pid:
logging.info("WirePlumber process terminated")
self.wireplumber_proc = None
if (self.pipewire_session_manager_proc is not None
and pid == self.pipewire_session_manager_proc.pid):
logging.info(self.pipewire_session_manager + " process terminated")
self.pipewire_session_manager_proc = None
pipewire_process = True

if pipewire_process:
self.pipewire_inhibitor.record_stopped(expected=False)
# Terminate other PipeWire-related processes to start fresh.
for proc, name in [(self.pipewire_proc, "pipewire"),
(self.pipewire_pulse_proc, "pipewire-pulse"),
(self.wireplumber_proc, "wireplumber")]:
(self.pipewire_session_manager_proc,
self.pipewire_session_manager)]:
if proc is not None:
terminate_process(proc.pid, name)
self.pipewire_proc = None
self.pipewire_pulse_proc = None
self.wireplumber_proc = None
self.pipewire_session_manager_proc = None

if self.session_proc is not None and pid == self.session_proc.pid:
logging.info("Session process terminated")
Expand Down Expand Up @@ -2543,7 +2570,7 @@ def main():
logging.info("Waiting before relaunching")
else:
if (desktop.pipewire_proc is None and desktop.pipewire_pulse_proc is None
and desktop.wireplumber_proc is None
and desktop.pipewire_session_manager_proc is None
and not desktop.pipewire_inhibitor.disabled
and desktop.pipewire_inhibitor.failures < MAX_LAUNCH_FAILURES):
desktop.setup_audio(host.host_id, backoff_time)
Expand Down
24 changes: 24 additions & 0 deletions remoting/host/linux/pipewire-media-session.conf.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
context.properties = {
remote.name = "$instance_name"
}

context.modules = [
{ name = libpipewire-module-protocol-native }
{ name = libpipewire-module-client-node }
{ name = libpipewire-module-client-device }
{ name = libpipewire-module-adapter }
{ name = libpipewire-module-metadata }

{ name = libpipewire-module-session-manager }
]

session.modules = {
default = [
flatpak # manages flatpak access
portal # manage portal permissions
suspend-node # suspend inactive nodes
policy-node # configure and link nodes
metadata # export metadata API
streams-follow-default # move streams when default changes
]
}

0 comments on commit 1de9788

Please sign in to comment.