audio: capture default sink's monitor, not default source
`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=<sink>.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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
+27
-1
@@ -121,6 +121,8 @@ pub async fn start(opts: &HostOpts) -> Result<CaptureHandle> {
|
||||
// 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<CaptureHandle> {
|
||||
"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<TcpStre
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn default_audio_monitor() -> Result<String> {
|
||||
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"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user