From 4cab9f6b207bafda2b9da31496e6fbe1b38278c3 Mon Sep 17 00:00:00 2001 From: Mollusk Date: Mon, 18 May 2026 04:42:42 -0400 Subject: [PATCH] audio: capture default sink's monitor, not default source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pulsesrc` with no `device=` reads PulseAudio's default source — which is the user's microphone, not system audio output. The stream was technically working but the laptop was hearing the desktop's mic (or silence on systems without one) instead of system audio. At host startup, shell out to `pactl get-default-sink` to discover the current default sink, then pass `device=.monitor` to pulsesrc. Resolving at session-start covers users who switch outputs (speakers vs headset vs HDMI) between sessions. pactl added to the host's required-binary list. Verified cross-machine: audio came through clearly with the prior ~1s latency floor preserved. Co-Authored-By: Claude Opus 4.7 --- src/common/deps.rs | 8 ++++++++ src/host/wayland.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/common/deps.rs b/src/common/deps.rs index 8c89d84..f36920f 100644 --- a/src/common/deps.rs +++ b/src/common/deps.rs @@ -8,6 +8,7 @@ pub fn check_host_binaries(display: DisplayServer) -> Result<()> { if display == DisplayServer::Wayland { require("gst-launch-1.0")?; require("gst-inspect-1.0")?; + require("pactl")?; require_gst_element("pipewiresrc")?; require_gst_element("vah264enc")?; require_gst_element("h264parse")?; @@ -61,6 +62,13 @@ fn install_hint_for_bin(bin: &str) -> String { Some("fedora" | "nobara") => "gstreamer1 gstreamer1-plugins-base-tools", _ => "gstreamer + tools", }, + "pactl" => match distro.as_deref() { + Some("arch" | "cachyos" | "manjaro" | "endeavouros") => "libpulse", + Some("debian" | "ubuntu" | "pop" | "linuxmint") => "pulseaudio-utils", + Some("fedora" | "nobara") => "pulseaudio-utils", + Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "pulseaudio-utils", + _ => "pulseaudio-utils (provides `pactl`)", + }, _ => bin, }; install_command(&distro, pkg) diff --git a/src/host/wayland.rs b/src/host/wayland.rs index 92a9986..bb30d0e 100644 --- a/src/host/wayland.rs +++ b/src/host/wayland.rs @@ -121,6 +121,8 @@ pub async fn start(opts: &HostOpts) -> Result { // no codec assumptions. let key_interval = (opts.framerate * 2).to_string(); let bitrate = opts.bitrate.to_string(); + let audio_monitor = default_audio_monitor().await?; + let audio_device = format!("device={audio_monitor}"); let mut gst_cmd = Command::new("gst-launch-1.0"); gst_cmd .args([ @@ -161,8 +163,10 @@ pub async fn start(opts: &HostOpts) -> Result { "video/x-h264,stream-format=byte-stream,alignment=au", "!", "mux.", - // audio branch + // audio branch — capture the default sink's MONITOR (system audio + // out), not the default source (which is the mic). "pulsesrc", + &audio_device, "do-timestamp=true", "!", "queue", @@ -274,3 +278,25 @@ pub async fn connect_to_capture(port: u16, max_wait: Duration) -> Result Result { + let output = Command::new("pactl") + .arg("get-default-sink") + .output() + .await + .context("failed to run `pactl get-default-sink` (install pulseaudio-utils or pipewire-pulse)")?; + if !output.status.success() { + bail!( + "pactl get-default-sink failed: {}", + String::from_utf8_lossy(&output.stderr).trim() + ); + } + let sink = String::from_utf8(output.stdout) + .context("default sink name was not UTF-8")? + .trim() + .to_string(); + if sink.is_empty() { + bail!("pactl get-default-sink returned no name (is a sound server running?)"); + } + Ok(format!("{sink}.monitor")) +}