Files
zed/crates/audio/src/audio_settings.rs
T
Jakub KonkaandZed Zippy e7926480dd settings: Add ability to select audio input/output devices for collab (#49015)
This PR adds ability to select and test audio input/output devices for
use in collaboration setting (which is what the team at Zed relies
heavily on). Currently, we only ever used whatever the system default is
and it worked well until it didn't - for some reason, when I am on my
Linux laptop, I am unable to force Zed to use my external mic +
headphones via external USB audio interface. With this PR, now I can
list all available devices and select the one I want.

There are still a couple of caveats that we should be aware of:
* I've decided to list *all* available devices meaning on Linux it is
quite possible that you may discover that what your desktop environment
is reporting to you is a significantly shorter list than what your sound
framework/hw is actually exposing. I think this makes sense given my
inexperience with audio drivers/devices and frameworks on various OSes
so that we get full control over what is available with the goal of
being able to come up with some filtering heuristic as we go along.
* We currently populate the list of available audio devices only once at
startup meaning if you unplug your device while you have Zed running
this will not register until you restart Zed which is a PITA. However,
in order to keep the changes manageable I thought it would be best to do
minimal work in this regard now, and iterate on this some more in the
near future. After all, we don't really monitor device changes on any
platform except macOS anyhow, so it might be the case that when I get
round to implementing this I will have the opportunity to tackle both at
the same time.
* In order to get a valid list of all audio devices using `cpal` crate
(which is the building block of `rodio`), I had to bump `cpal` to 0.17,
and pin `rodio` to a more recent commit sha as a result, so if you see
any regressions, lemme know and/or feel free to revert this PR.
* Finally, I've done my best to integrate this with the settings UI, but
I am sure more could be done in terms of styling, etc.

Some screenshots:

<img width="1152" height="949" alt="Screenshot From 2026-02-12 11-40-04"
src="https://github.com/user-attachments/assets/e147c153-1902-49d6-bf68-3ac317a6a7b0"
/>
<img width="1152" height="949" alt="Screenshot From 2026-02-12 11-40-16"
src="https://github.com/user-attachments/assets/b4e9a2f8-b38e-4de0-b910-067cc432b5bc"
/>


Release Notes:

- Added ability to select audio input/output devices as part of
Collaboration page in Settings. Added ability to test selected devices
with a simple playback loop routing input directly into output for
easier debugging of your audio devices.

---------

Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
2026-02-12 14:27:55 +01:00

140 lines
5.2 KiB
Rust

use std::{
str::FromStr,
sync::atomic::{AtomicBool, Ordering},
};
use cpal::DeviceId;
use gpui::App;
use settings::{RegisterSetting, Settings, SettingsStore};
#[derive(Clone, Debug, RegisterSetting)]
pub struct AudioSettings {
/// Opt into the new audio system.
///
/// You need to rejoin a call for this setting to apply
pub rodio_audio: bool, // default is false
/// Requires 'rodio_audio: true'
///
/// Automatically increase or decrease you microphone's volume. This affects how
/// loud you sound to others.
///
/// Recommended: off (default)
/// Microphones are too quite in zed, until everyone is on experimental
/// audio and has auto speaker volume on this will make you very loud
/// compared to other speakers.
pub auto_microphone_volume: bool,
/// Requires 'rodio_audio: true'
///
/// Automatically increate or decrease the volume of other call members.
/// This only affects how things sound for you.
pub auto_speaker_volume: bool,
/// Requires 'rodio_audio: true'
///
/// Remove background noises. Works great for typing, cars, dogs, AC. Does
/// not work well on music.
pub denoise: bool,
/// Requires 'rodio_audio: true'
///
/// Use audio parameters compatible with the previous versions of
/// experimental audio and non-experimental audio. When this is false you
/// will sound strange to anyone not on the latest experimental audio. In
/// the future we will migrate by setting this to false
///
/// You need to rejoin a call for this setting to apply
pub legacy_audio_compatible: bool,
/// Requires 'rodio_audio: true'
///
/// Select specific output audio device.
pub output_audio_device: Option<DeviceId>,
/// Requires 'rodio_audio: true'
///
/// Select specific input audio device.
pub input_audio_device: Option<DeviceId>,
}
/// Configuration of audio in Zed
impl Settings for AudioSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
let audio = &content.audio.as_ref().unwrap();
AudioSettings {
rodio_audio: audio.rodio_audio.unwrap(),
auto_microphone_volume: audio.auto_microphone_volume.unwrap(),
auto_speaker_volume: audio.auto_speaker_volume.unwrap(),
denoise: audio.denoise.unwrap(),
legacy_audio_compatible: audio.legacy_audio_compatible.unwrap(),
output_audio_device: audio
.output_audio_device
.as_ref()
.and_then(|x| x.0.as_ref().and_then(|id| DeviceId::from_str(&id).ok())),
input_audio_device: audio
.input_audio_device
.as_ref()
.and_then(|x| x.0.as_ref().and_then(|id| DeviceId::from_str(&id).ok())),
}
}
}
/// See docs on [LIVE_SETTINGS]
pub(crate) struct LiveSettings {
pub(crate) auto_microphone_volume: AtomicBool,
pub(crate) auto_speaker_volume: AtomicBool,
pub(crate) denoise: AtomicBool,
}
impl LiveSettings {
pub(crate) fn initialize(&self, cx: &mut App) {
cx.observe_global::<SettingsStore>(move |cx| {
LIVE_SETTINGS.auto_microphone_volume.store(
AudioSettings::get_global(cx).auto_microphone_volume,
Ordering::Relaxed,
);
LIVE_SETTINGS.auto_speaker_volume.store(
AudioSettings::get_global(cx).auto_speaker_volume,
Ordering::Relaxed,
);
let denoise_enabled = AudioSettings::get_global(cx).denoise;
#[cfg(debug_assertions)]
{
static DENOISE_WARNING_SEND: AtomicBool = AtomicBool::new(false);
if denoise_enabled && !DENOISE_WARNING_SEND.load(Ordering::Relaxed) {
DENOISE_WARNING_SEND.store(true, Ordering::Relaxed);
log::warn!("Denoise does not work on debug builds, not enabling")
}
}
#[cfg(not(debug_assertions))]
LIVE_SETTINGS
.denoise
.store(denoise_enabled, Ordering::Relaxed);
})
.detach();
let init_settings = AudioSettings::get_global(cx);
LIVE_SETTINGS
.auto_microphone_volume
.store(init_settings.auto_microphone_volume, Ordering::Relaxed);
LIVE_SETTINGS
.auto_speaker_volume
.store(init_settings.auto_speaker_volume, Ordering::Relaxed);
let denoise_enabled = AudioSettings::get_global(cx).denoise;
#[cfg(debug_assertions)]
if denoise_enabled {
log::warn!("Denoise does not work on debug builds, not enabling")
}
#[cfg(not(debug_assertions))]
LIVE_SETTINGS
.denoise
.store(denoise_enabled, Ordering::Relaxed);
}
}
/// Allows access to settings from the audio thread. Updated by
/// observer of SettingsStore. Needed because audio playback and recording are
/// real time and must each run in a dedicated OS thread, therefore we can not
/// use the background executor.
pub(crate) static LIVE_SETTINGS: LiveSettings = LiveSettings {
auto_microphone_volume: AtomicBool::new(true),
auto_speaker_volume: AtomicBool::new(true),
denoise: AtomicBool::new(true),
};