From 28687ff9d1d75157567260b7dc484e49075d773d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 24 Mar 2026 00:58:09 +0100 Subject: [PATCH] audio: Remove rodio audio backend (#51945) Co-authored-by: Smit Barmase We've decided to remove rodio backend for audio as we didn't have time to get it to a nice spot. I personally believe we should eventually re-land it (the composable pipelines are quite nice!), but given that we need audio to work, this seems like the best way forward. We won't have to worry about all the ways in which the legacy pipeline interoped with rodio's. ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - audio: Experimental rodio audio backend has been removed. --------- Co-authored-by: Smit Barmase Co-authored-by: Yara --- Cargo.lock | 2 - assets/settings/default.json | 27 ---- crates/audio/Cargo.toml | 2 - crates/audio/src/audio.rs | 2 +- crates/audio/src/audio_pipeline.rs | 114 +---------------- crates/audio/src/audio_pipeline/replays.rs | 78 ------------ crates/audio/src/audio_settings.rs | 63 ---------- crates/livekit_client/src/livekit_client.rs | 31 ++--- .../src/livekit_client/playback.rs | 116 ++---------------- .../src/livekit_client/playback/source.rs | 93 -------------- .../settings_content/src/settings_content.rs | 31 ----- crates/settings_ui/src/page_data.rs | 97 +-------------- crates/zed/src/zed.rs | 96 +-------------- 13 files changed, 26 insertions(+), 726 deletions(-) delete mode 100644 crates/audio/src/audio_pipeline/replays.rs delete mode 100644 crates/livekit_client/src/livekit_client/playback/source.rs diff --git a/Cargo.lock b/Cargo.lock index a3ddf3f4960224f4ebf46c4850f7214d3fc493d1..63ae6c4c708a452d4ce9e694c325970428f17540 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1278,7 +1278,6 @@ name = "audio" version = "0.1.0" dependencies = [ "anyhow", - "async-tar", "collections", "cpal", "crossbeam", @@ -1290,7 +1289,6 @@ dependencies = [ "rodio", "serde", "settings", - "smol", "thiserror 2.0.17", "util", ] diff --git a/assets/settings/default.json b/assets/settings/default.json index 959af6a021b0312fda29ece92bc3d31b2bd3c7d7..05d9b592979f19184f6e1b9b9cd6c7b02c603ca1 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -464,10 +464,6 @@ "button_layout": "platform_default", }, "audio": { - // Opt into the new audio system. - "experimental.rodio_audio": false, - // Requires 'rodio_audio: true' - // // Automatically increase or decrease you microphone's volume. This affects how // loud you sound to others. // @@ -476,33 +472,10 @@ // audio and has auto speaker volume on this will make you very loud // compared to other speakers. "experimental.auto_microphone_volume": false, - // Requires 'rodio_audio: true' - // - // Automatically increate or decrease the volume of other call members. - // This only affects how things sound for you. - "experimental.auto_speaker_volume": true, - // Requires 'rodio_audio: true' - // - // Remove background noises. Works great for typing, cars, dogs, AC. Does - // not work well on music. - "experimental.denoise": true, - // 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 - "experimental.legacy_audio_compatible": true, - // Requires 'rodio_audio: true' - // // Select specific output audio device. // `null` means use system default. // Any unrecognized output device will fall back to system default. "experimental.output_audio_device": null, - // Requires 'rodio_audio: true' - // // Select specific input audio device. // `null` means use system default. // Any unrecognized input device will fall back to system default. diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index f3898265e500dd40602c9877b5e4c0980932a81a..59df698af14aa611e44b4bb0f69ac4eb9c83ba10 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -14,7 +14,6 @@ doctest = false [dependencies] anyhow.workspace = true -async-tar.workspace = true collections.workspace = true cpal.workspace = true crossbeam.workspace = true @@ -25,7 +24,6 @@ parking_lot.workspace = true rodio.workspace = true serde.workspace = true settings.workspace = true -smol.workspace = true thiserror.workspace = true util.workspace = true diff --git a/crates/audio/src/audio.rs b/crates/audio/src/audio.rs index bfd30973923027e9ed5080fbee005abe4c0fd912..24a4032d954c2eb8282872c544bc240a0e1dabe8 100644 --- a/crates/audio/src/audio.rs +++ b/crates/audio/src/audio.rs @@ -11,7 +11,7 @@ pub use audio_settings::AudioSettings; pub use audio_settings::LIVE_SETTINGS; mod audio_pipeline; -pub use audio_pipeline::{Audio, VoipParts}; +pub use audio_pipeline::Audio; pub use audio_pipeline::{AudioDeviceInfo, AvailableAudioDevices}; pub use audio_pipeline::{ensure_devices_initialized, resolve_device}; // TODO(audio) replace with input test functionality in the audio crate diff --git a/crates/audio/src/audio_pipeline.rs b/crates/audio/src/audio_pipeline.rs index 3d2a6ae32c381b1cab590946c35fbb68325af5db..a2b353563bdf7b82df86a180617c21ccff95496d 100644 --- a/crates/audio/src/audio_pipeline.rs +++ b/crates/audio/src/audio_pipeline.rs @@ -4,23 +4,17 @@ use cpal::{ DeviceDescription, DeviceId, default_host, traits::{DeviceTrait, HostTrait}, }; -use gpui::{App, AsyncApp, BackgroundExecutor, BorrowAppContext, Global}; +use gpui::{App, AsyncApp, BorrowAppContext, Global}; pub(super) use cpal::Sample; -pub(super) use rodio::source::LimitSettings; -use rodio::{ - Decoder, DeviceSinkBuilder, MixerDeviceSink, Source, - mixer::Mixer, - source::{AutomaticGainControlSettings, Buffered}, -}; +use rodio::{Decoder, DeviceSinkBuilder, MixerDeviceSink, Source, mixer::Mixer, source::Buffered}; use settings::Settings; -use std::{io::Cursor, path::PathBuf, sync::atomic::Ordering, time::Duration}; +use std::io::Cursor; use util::ResultExt; mod echo_canceller; use echo_canceller::EchoCanceller; -mod replays; mod rodio_ext; pub use crate::audio_settings::AudioSettings; pub use rodio_ext::RodioExt; @@ -59,7 +53,6 @@ pub struct Audio { output: Option<(MixerDeviceSink, Mixer)>, pub echo_canceller: EchoCanceller, source_cache: HashMap>>>>, - replays: replays::Replays, } impl Global for Audio {} @@ -84,84 +77,6 @@ impl Audio { .expect("we only get here if opening the outputstream succeeded")) } - pub fn save_replays( - &self, - executor: BackgroundExecutor, - ) -> gpui::Task> { - self.replays.replays_to_tar(executor) - } - - pub fn open_microphone(mut voip_parts: VoipParts) -> anyhow::Result { - let stream = open_input_stream(voip_parts.input_audio_device)?; - let stream = stream - .possibly_disconnected_channels_to_mono() - .constant_params(CHANNEL_COUNT, SAMPLE_RATE) - .process_buffer::(move |buffer| { - let mut int_buffer: [i16; _] = buffer.map(|s| s.to_sample()); - if voip_parts - .echo_canceller - .process_stream(&mut int_buffer) - .log_err() - .is_some() - { - for (sample, processed) in buffer.iter_mut().zip(&int_buffer) { - *sample = (*processed).to_sample(); - } - } - }) - .limit(LimitSettings::live_performance()) - .automatic_gain_control(AutomaticGainControlSettings { - target_level: 0.90, - attack_time: Duration::from_secs(1), - release_time: Duration::from_secs(0), - absolute_max_gain: 5.0, - }) - .periodic_access(Duration::from_millis(100), move |agc_source| { - agc_source - .set_enabled(LIVE_SETTINGS.auto_microphone_volume.load(Ordering::Relaxed)); - let _ = LIVE_SETTINGS.denoise; // TODO(audio: re-introduce de-noising - }); - - let (replay, stream) = stream.replayable(crate::REPLAY_DURATION)?; - voip_parts - .replays - .add_voip_stream("local microphone".to_string(), replay); - - Ok(stream) - } - - pub fn play_voip_stream( - source: impl rodio::Source + Send + 'static, - speaker_name: String, - is_staff: bool, - cx: &mut App, - ) -> anyhow::Result<()> { - let (replay_source, source) = source - .automatic_gain_control(AutomaticGainControlSettings { - target_level: 0.90, - attack_time: Duration::from_secs(1), - release_time: Duration::from_secs(0), - absolute_max_gain: 5.0, - }) - .periodic_access(Duration::from_millis(100), move |agc_source| { - agc_source.set_enabled(LIVE_SETTINGS.auto_speaker_volume.load(Ordering::Relaxed)); - }) - .replayable(crate::REPLAY_DURATION) - .expect("REPLAY_DURATION is longer than 100ms"); - let output_audio_device = AudioSettings::get_global(cx).output_audio_device.clone(); - - cx.update_default_global(|this: &mut Self, _cx| { - let output_mixer = this - .ensure_output_exists(output_audio_device) - .context("Could not get output mixer")?; - output_mixer.add(source); - if is_staff { - this.replays.add_voip_stream(speaker_name, replay_source); - } - Ok(()) - }) - } - pub fn play_sound(sound: Sound, cx: &mut App) { let output_audio_device = AudioSettings::get_global(cx).output_audio_device.clone(); cx.update_default_global(|this: &mut Self, cx| { @@ -203,29 +118,6 @@ impl Audio { } } -pub struct VoipParts { - echo_canceller: EchoCanceller, - replays: replays::Replays, - input_audio_device: Option, -} - -impl VoipParts { - pub fn new(cx: &AsyncApp) -> anyhow::Result { - let (apm, replays) = cx.read_default_global::(|audio, _| { - (audio.echo_canceller.clone(), audio.replays.clone()) - }); - let input_audio_device = - AudioSettings::try_read_global(cx, |settings| settings.input_audio_device.clone()) - .flatten(); - - Ok(Self { - echo_canceller: apm, - replays, - input_audio_device, - }) - } -} - pub fn open_input_stream( device_id: Option, ) -> anyhow::Result { diff --git a/crates/audio/src/audio_pipeline/replays.rs b/crates/audio/src/audio_pipeline/replays.rs deleted file mode 100644 index 3228700b2df5581e862da6ec71787704985386a2..0000000000000000000000000000000000000000 --- a/crates/audio/src/audio_pipeline/replays.rs +++ /dev/null @@ -1,78 +0,0 @@ -use anyhow::{Context, anyhow}; -use async_tar::{Builder, Header}; -use gpui::{BackgroundExecutor, Task}; - -use collections::HashMap; -use parking_lot::Mutex; -use rodio::Source; -use smol::fs::File; -use std::{io, path::PathBuf, sync::Arc, time::Duration}; - -use crate::REPLAY_DURATION; -use crate::audio_pipeline::rodio_ext::Replay; - -#[derive(Default, Clone)] -pub(crate) struct Replays(Arc>>); - -impl Replays { - pub(crate) fn add_voip_stream(&self, stream_name: String, source: Replay) { - let mut map = self.0.lock(); - map.retain(|_, replay| replay.source_is_active()); - map.insert(stream_name, source); - } - - pub(crate) fn replays_to_tar( - &self, - executor: BackgroundExecutor, - ) -> Task> { - let map = Arc::clone(&self.0); - executor.spawn(async move { - let recordings: Vec<_> = map - .lock() - .iter_mut() - .map(|(name, replay)| { - let queued = REPLAY_DURATION.min(replay.duration_ready()); - (name.clone(), replay.take_duration(queued).record()) - }) - .collect(); - let longest = recordings - .iter() - .map(|(_, r)| { - r.total_duration() - .expect("SamplesBuffer always returns a total duration") - }) - .max() - .ok_or(anyhow!("There is no audio to capture"))?; - - let path = std::env::current_dir() - .context("Could not get current dir")? - .join("replays.tar"); - let tar = File::create(&path) - .await - .context("Could not create file for tar")?; - - let mut tar = Builder::new(tar); - - for (name, recording) in recordings { - let mut writer = io::Cursor::new(Vec::new()); - rodio::wav_to_writer(recording, &mut writer).context("failed to encode wav")?; - let wav_data = writer.into_inner(); - let path = name.replace(' ', "_") + ".wav"; - let mut header = Header::new_gnu(); - // rw permissions for everyone - header.set_mode(0o666); - header.set_size(wav_data.len() as u64); - tar.append_data(&mut header, path, wav_data.as_slice()) - .await - .context("failed to apped wav to tar")?; - } - tar.into_inner() - .await - .context("Could not finish writing tar")? - .sync_all() - .await - .context("Could not flush tar file to disk")?; - Ok((path, longest)) - }) - } -} diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index 109bff605cabde402e47f5c7015cbbaefcd6a637..e200e232888e91a58f7776ff92b7b65de5f9b318 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -9,12 +9,6 @@ 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. /// @@ -23,25 +17,6 @@ pub struct AudioSettings { /// 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, /// Select specific output audio device. pub output_audio_device: Option, /// Select specific input audio device. @@ -53,11 +28,7 @@ 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() @@ -73,8 +44,6 @@ impl Settings for AudioSettings { /// See docs on [LIVE_SETTINGS] pub struct LiveSettings { pub auto_microphone_volume: AtomicBool, - pub(crate) auto_speaker_volume: AtomicBool, - pub(crate) denoise: AtomicBool, } impl LiveSettings { @@ -84,24 +53,6 @@ impl LiveSettings { 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(); @@ -109,18 +60,6 @@ impl LiveSettings { 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); } } @@ -130,6 +69,4 @@ impl LiveSettings { /// use the background executor. pub static LIVE_SETTINGS: LiveSettings = LiveSettings { auto_microphone_volume: AtomicBool::new(true), - auto_speaker_volume: AtomicBool::new(true), - denoise: AtomicBool::new(true), }; diff --git a/crates/livekit_client/src/livekit_client.rs b/crates/livekit_client/src/livekit_client.rs index 57b7f7c42e9f684497d508d7404a69ebc4fb6666..1c1cc5c3b7075b90950d85bbc92ba186a4f415ba 100644 --- a/crates/livekit_client/src/livekit_client.rs +++ b/crates/livekit_client/src/livekit_client.rs @@ -1,10 +1,10 @@ -use anyhow::{Context as _, Result, anyhow}; +use anyhow::{Context as _, Result}; use audio::AudioSettings; use collections::HashMap; use futures::{SinkExt, channel::mpsc}; use gpui::{App, AsyncApp, ScreenCaptureSource, ScreenCaptureStream, Task}; use gpui_tokio::Tokio; -use log::info; + use playback::capture_local_video_track; use settings::Settings; use std::sync::{Arc, atomic::AtomicU64}; @@ -13,10 +13,7 @@ use std::sync::{Arc, atomic::AtomicU64}; mod linux; mod playback; -use crate::{ - ConnectionQuality, LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication, - livekit_client::playback::Speaker, -}; +use crate::{ConnectionQuality, LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication}; pub use livekit::SessionStats; pub use livekit::webrtc::stats::RtcStats; pub use playback::AudioStream; @@ -144,24 +141,10 @@ impl Room { track: &RemoteAudioTrack, cx: &mut App, ) -> Result { - let speaker: Speaker = - serde_urlencoded::from_str(&track.0.name()).unwrap_or_else(|_| Speaker { - name: track.0.name(), - is_staff: false, - sends_legacy_audio: true, - }); - - if AudioSettings::get_global(cx).rodio_audio { - info!("Using experimental.rodio_audio audio pipeline for output"); - playback::play_remote_audio_track(&track.0, speaker, cx) - } else if speaker.sends_legacy_audio { - let output_audio_device = AudioSettings::get_global(cx).output_audio_device.clone(); - Ok(self - .playback - .play_remote_audio_track(&track.0, output_audio_device)) - } else { - Err(anyhow!("Client version too old to play audio in call")) - } + let output_audio_device = AudioSettings::get_global(cx).output_audio_device.clone(); + Ok(self + .playback + .play_remote_audio_track(&track.0, output_audio_device)) } pub async fn get_stats(&self) -> Result { diff --git a/crates/livekit_client/src/livekit_client/playback.rs b/crates/livekit_client/src/livekit_client/playback.rs index b887af10553e71cbe8dfa6f197538ee592daac03..cea5b1169b0c1c0c6b699884e107cf24795f5d9c 100644 --- a/crates/livekit_client/src/livekit_client/playback.rs +++ b/crates/livekit_client/src/livekit_client/playback.rs @@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize}; use settings::Settings; use std::cell::RefCell; use std::sync::Weak; -use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering}; +use std::sync::atomic::{AtomicI32, AtomicU64, Ordering}; use std::time::{Duration, Instant}; use std::{borrow::Cow, collections::VecDeque, sync::Arc}; use util::{ResultExt as _, maybe}; @@ -39,8 +39,6 @@ struct TimestampedFrame { captured_at: Instant, } -mod source; - pub(crate) struct AudioStack { executor: BackgroundExecutor, apm: Arc>, @@ -49,38 +47,6 @@ pub(crate) struct AudioStack { next_ssrc: AtomicI32, } -pub(crate) fn play_remote_audio_track( - track: &livekit::track::RemoteAudioTrack, - speaker: Speaker, - cx: &mut gpui::App, -) -> Result { - info!("speaker: {speaker:?}"); - let stream = - source::LiveKitStream::new(cx.background_executor(), track, speaker.sends_legacy_audio); - - let stop_handle = Arc::new(AtomicBool::new(false)); - let stop_handle_clone = stop_handle.clone(); - let stream = stream - .stoppable() - .periodic_access(Duration::from_millis(50), move |s| { - if stop_handle.load(Ordering::Relaxed) { - s.stop(); - } - }); - - info!("sample_rate: {:?}", stream.sample_rate()); - info!("channel_count: {:?}", stream.channels()); - audio::Audio::play_voip_stream(stream, speaker.name, speaker.is_staff, cx) - .context("Could not play audio")?; - - let on_drop = util::defer(move || { - stop_handle_clone.store(true, Ordering::Relaxed); - }); - Ok(AudioStream::Output { - _drop: Box::new(on_drop), - }) -} - impl AudioStack { pub(crate) fn new(executor: BackgroundExecutor) -> Self { let apm = Arc::new(Mutex::new(apm::AudioProcessingModule::new( @@ -170,33 +136,17 @@ impl AudioStack { is_staff: bool, cx: &AsyncApp, ) -> Result<(crate::LocalAudioTrack, AudioStream, Arc)> { - let legacy_audio_compatible = - AudioSettings::try_read_global(cx, |setting| setting.legacy_audio_compatible) - .unwrap_or(true); - - let source = if legacy_audio_compatible { - NativeAudioSource::new( - // n.b. this struct's options are always ignored, noise cancellation is provided by apm. - AudioSourceOptions::default(), - SAMPLE_RATE.get(), // TODO(audio): this was legacy params, - // removed for now for simplicity - CHANNEL_COUNT.get().into(), - 10, - ) - } else { - NativeAudioSource::new( - // n.b. this struct's options are always ignored, noise cancellation is provided by apm. - AudioSourceOptions::default(), - SAMPLE_RATE.get(), - CHANNEL_COUNT.get().into(), - 10, - ) - }; + let source = NativeAudioSource::new( + // n.b. this struct's options are always ignored, noise cancellation is provided by apm. + AudioSourceOptions::default(), + SAMPLE_RATE.get(), + CHANNEL_COUNT.get().into(), + 10, + ); let speaker = Speaker { name: user_name, is_staff, - sends_legacy_audio: legacy_audio_compatible, }; log::info!("Microphone speaker: {speaker:?}"); let track_name = serde_urlencoded::to_string(speaker) @@ -221,21 +171,7 @@ impl AudioStack { } } }); - let rodio_pipeline = - AudioSettings::try_read_global(cx, |setting| setting.rodio_audio).unwrap_or_default(); - let capture_task = if rodio_pipeline { - info!("Using experimental.rodio_audio audio pipeline"); - let voip_parts = audio::VoipParts::new(cx)?; - // Audio needs to run real-time and should never be paused. That is - // why we are using a normal std::thread and not a background task - self.executor - .spawn_with_priority(Priority::RealtimeAudio, async move { - // microphone is non send on mac - let microphone = audio::Audio::open_microphone(voip_parts)?; - send_to_livekit(frame_tx, microphone); - Ok(()) - }) - } else { + let capture_task = { let input_audio_device = AudioSettings::try_read_global(cx, |settings| settings.input_audio_device.clone()) .flatten(); @@ -546,40 +482,6 @@ impl rodio::Source for RodioEffectsAdaptor { pub struct Speaker { pub name: String, pub is_staff: bool, - pub sends_legacy_audio: bool, -} - -fn send_to_livekit(mut frame_tx: Sender, mut microphone: impl Source) { - use cpal::Sample; - let sample_rate = microphone.sample_rate().get(); - let num_channels = microphone.channels().get() as u32; - let buffer_size = sample_rate / 100 * num_channels; - - loop { - let sampled: Vec<_> = microphone - .by_ref() - .take(buffer_size as usize) - .map(|s| s.to_sample()) - .collect(); - - match frame_tx.try_send(TimestampedFrame { - frame: AudioFrame { - sample_rate, - num_channels, - samples_per_channel: sampled.len() as u32 / num_channels, - data: Cow::Owned(sampled), - }, - captured_at: Instant::now(), - }) { - Ok(_) => {} - Err(err) => { - if !err.is_full() { - // must rx has dropped or is not consuming - break; - } - } - } - } } use super::LocalVideoTrack; diff --git a/crates/livekit_client/src/livekit_client/playback/source.rs b/crates/livekit_client/src/livekit_client/playback/source.rs deleted file mode 100644 index 2738109ff8fc972e9ab53768fd212d6f5ff5f194..0000000000000000000000000000000000000000 --- a/crates/livekit_client/src/livekit_client/playback/source.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::num::NonZero; - -use futures::StreamExt; -use libwebrtc::{audio_stream::native::NativeAudioStream, prelude::AudioFrame}; -use livekit::track::RemoteAudioTrack; -use rodio::{ - ChannelCount, SampleRate, Source, buffer::SamplesBuffer, conversions::SampleTypeConverter, -}; - -use audio::{CHANNEL_COUNT, SAMPLE_RATE}; - -fn frame_to_samplesbuffer(frame: AudioFrame) -> SamplesBuffer { - let samples = frame.data.iter().copied(); - let samples = SampleTypeConverter::<_, _>::new(samples); - let samples: Vec = samples.collect(); - SamplesBuffer::new( - NonZero::new(frame.num_channels as u16).expect("zero channels is nonsense"), - NonZero::new(frame.sample_rate).expect("samplerate zero is nonsense"), - samples, - ) -} - -pub struct LiveKitStream { - // shared_buffer: SharedBuffer, - inner: rodio::queue::SourcesQueueOutput, - _receiver_task: gpui::Task<()>, - channel_count: ChannelCount, - sample_rate: SampleRate, -} - -impl LiveKitStream { - pub fn new( - executor: &gpui::BackgroundExecutor, - track: &RemoteAudioTrack, - legacy: bool, - ) -> Self { - let (channel_count, sample_rate) = if legacy { - // (LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE) TODO(audio): do this or remove - (CHANNEL_COUNT, SAMPLE_RATE) - } else { - (CHANNEL_COUNT, SAMPLE_RATE) - }; - - let mut stream = NativeAudioStream::new( - track.rtc_track(), - sample_rate.get() as i32, - channel_count.get().into(), - ); - let (queue_input, queue_output) = rodio::queue::queue(true); - // spawn rtc stream - let receiver_task = executor.spawn_with_priority(gpui::Priority::RealtimeAudio, { - async move { - while let Some(frame) = stream.next().await { - let samples = frame_to_samplesbuffer(frame); - queue_input.append(samples); - } - } - }); - - LiveKitStream { - _receiver_task: receiver_task, - inner: queue_output, - sample_rate, - channel_count, - } - } -} - -impl Iterator for LiveKitStream { - type Item = rodio::Sample; - - fn next(&mut self) -> Option { - self.inner.next() - } -} - -impl Source for LiveKitStream { - fn current_span_len(&self) -> Option { - self.inner.current_span_len() - } - - fn channels(&self) -> rodio::ChannelCount { - self.channel_count - } - - fn sample_rate(&self) -> rodio::SampleRate { - self.sample_rate - } - - fn total_duration(&self) -> Option { - self.inner.total_duration() - } -} diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index 731f576653b0f6f6403575edf059c34d722a3d12..5762142d9bde1b1f3631f66877889d2a2bcf07e2 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -322,13 +322,6 @@ impl strum::VariantNames for BaseKeymapContent { #[with_fallible_options] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct AudioSettingsContent { - /// Opt into the new audio system. - /// - /// You need to rejoin a call for this setting to apply - #[serde(rename = "experimental.rodio_audio")] - pub rodio_audio: Option, // default is false - /// Requires 'rodio_audio: true' - /// /// Automatically increase or decrease you microphone's volume. This affects how /// loud you sound to others. /// @@ -338,35 +331,11 @@ pub struct AudioSettingsContent { /// compared to other speakers. #[serde(rename = "experimental.auto_microphone_volume")] pub auto_microphone_volume: Option, - /// Requires 'rodio_audio: true' - /// - /// Automatically increate or decrease the volume of other call members. - /// This only affects how things sound for you. - #[serde(rename = "experimental.auto_speaker_volume")] - pub auto_speaker_volume: Option, - /// Requires 'rodio_audio: true' - /// /// Remove background noises. Works great for typing, cars, dogs, AC. Does /// not work well on music. - #[serde(rename = "experimental.denoise")] - pub denoise: Option, - /// 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 - #[serde(rename = "experimental.legacy_audio_compatible")] - pub legacy_audio_compatible: Option, - /// Requires 'rodio_audio: true' - /// /// Select specific output audio device. #[serde(rename = "experimental.output_audio_device")] pub output_audio_device: Option, - /// Requires 'rodio_audio: true' - /// /// Select specific input audio device. #[serde(rename = "experimental.input_audio_device")] pub input_audio_device: Option, diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index d2132d0a9dcd932ef66a3fa874a5d1e7714fb726..634ba8c4fe5273e993d36a253d5e5b14a4085855 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -7038,101 +7038,8 @@ fn collaboration_page() -> SettingsPage { ] } - fn experimental_section() -> [SettingsPageItem; 9] { + fn audio_settings() -> [SettingsPageItem; 3] { [ - SettingsPageItem::SectionHeader("Experimental"), - SettingsPageItem::SettingItem(SettingItem { - title: "Rodio Audio", - description: "Opt into the new audio system.", - field: Box::new(SettingField { - json_path: Some("audio.experimental.rodio_audio"), - pick: |settings_content| settings_content.audio.as_ref()?.rodio_audio.as_ref(), - write: |settings_content, value| { - settings_content.audio.get_or_insert_default().rodio_audio = value; - }, - }), - metadata: None, - files: USER, - }), - SettingsPageItem::SettingItem(SettingItem { - title: "Auto Microphone Volume", - description: "Automatically adjust microphone volume (requires rodio audio).", - field: Box::new(SettingField { - json_path: Some("audio.experimental.auto_microphone_volume"), - pick: |settings_content| { - settings_content - .audio - .as_ref()? - .auto_microphone_volume - .as_ref() - }, - write: |settings_content, value| { - settings_content - .audio - .get_or_insert_default() - .auto_microphone_volume = value; - }, - }), - metadata: None, - files: USER, - }), - SettingsPageItem::SettingItem(SettingItem { - title: "Auto Speaker Volume", - description: "Automatically adjust volume of other call members (requires rodio audio).", - field: Box::new(SettingField { - json_path: Some("audio.experimental.auto_speaker_volume"), - pick: |settings_content| { - settings_content - .audio - .as_ref()? - .auto_speaker_volume - .as_ref() - }, - write: |settings_content, value| { - settings_content - .audio - .get_or_insert_default() - .auto_speaker_volume = value; - }, - }), - metadata: None, - files: USER, - }), - SettingsPageItem::SettingItem(SettingItem { - title: "Denoise", - description: "Remove background noises (requires rodio audio).", - field: Box::new(SettingField { - json_path: Some("audio.experimental.denoise"), - pick: |settings_content| settings_content.audio.as_ref()?.denoise.as_ref(), - write: |settings_content, value| { - settings_content.audio.get_or_insert_default().denoise = value; - }, - }), - metadata: None, - files: USER, - }), - SettingsPageItem::SettingItem(SettingItem { - title: "Legacy Audio Compatible", - description: "Use audio parameters compatible with previous versions (requires rodio audio).", - field: Box::new(SettingField { - json_path: Some("audio.experimental.legacy_audio_compatible"), - pick: |settings_content| { - settings_content - .audio - .as_ref()? - .legacy_audio_compatible - .as_ref() - }, - write: |settings_content, value| { - settings_content - .audio - .get_or_insert_default() - .legacy_audio_compatible = value; - }, - }), - metadata: None, - files: USER, - }), SettingsPageItem::ActionLink(ActionLink { title: "Test Audio".into(), description: Some("Test your microphone and speaker setup".into()), @@ -7193,7 +7100,7 @@ fn collaboration_page() -> SettingsPage { SettingsPage { title: "Collaboration", - items: concat_sections![calls_section(), experimental_section()], + items: concat_sections![calls_section(), audio_settings()], } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3d933e35c684fbf55d401d28831e5e844b456e16..0372494f87c3264504599c40ef40f14104cdeed4 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -17,7 +17,7 @@ use agent_ui::{AgentDiffToolbar, AgentPanelDelegate}; use anyhow::Context as _; pub use app_menus::*; use assets::Assets; -use audio::{AudioSettings, REPLAY_DURATION}; + use breadcrumbs::Breadcrumbs; use client::zed_urls; use collections::VecDeque; @@ -69,7 +69,7 @@ use settings::{ update_settings_file, }; use sidebar::Sidebar; -use std::time::Duration; + use std::{ borrow::Cow, path::{Path, PathBuf}, @@ -84,9 +84,7 @@ use util::rel_path::RelPath; use util::{ResultExt, asset_str, maybe}; use uuid::Uuid; use vim_mode_setting::VimModeSetting; -use workspace::notifications::{ - NotificationId, SuppressEvent, dismiss_app_notification, show_app_notification, -}; +use workspace::notifications::{NotificationId, dismiss_app_notification, show_app_notification}; use workspace::{ AppState, MultiWorkspace, NewFile, NewWindow, OpenLog, Panel, Toast, Workspace, @@ -94,8 +92,7 @@ use workspace::{ notifications::simple_message_notification::MessageNotification, open_new, }; use workspace::{ - CloseIntent, CloseProject, CloseWindow, NotificationFrame, RestoreBanner, - with_active_or_new_workspace, + CloseIntent, CloseProject, CloseWindow, RestoreBanner, with_active_or_new_workspace, }; use workspace::{Pane, notifications::DetachAndPromptErr}; use zed_actions::{ @@ -144,10 +141,6 @@ actions!( actions!( dev, [ - /// Stores last 30s of audio from zed staff using the experimental rodio - /// audio system (including yourself) on the current call in a tar file - /// in the current working directory. - CaptureRecentAudio, /// Opens a prompt to enter a URL to open. OpenUrlPrompt, ] @@ -1148,9 +1141,6 @@ fn register_actions( .detach_and_log_err(cx); } } - }) - .register_action(|workspace, _: &CaptureRecentAudio, window, cx| { - capture_recent_audio(workspace, window, cx); }); #[cfg(not(target_os = "windows"))] @@ -2141,84 +2131,6 @@ fn open_settings_file( .detach_and_log_err(cx); } -fn capture_recent_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Context) { - struct CaptureRecentAudioNotification { - focus_handle: gpui::FocusHandle, - save_result: Option>, - _save_task: Task>, - } - - impl gpui::EventEmitter for CaptureRecentAudioNotification {} - impl gpui::EventEmitter for CaptureRecentAudioNotification {} - impl gpui::Focusable for CaptureRecentAudioNotification { - fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle { - self.focus_handle.clone() - } - } - impl workspace::notifications::Notification for CaptureRecentAudioNotification {} - - impl Render for CaptureRecentAudioNotification { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let message = match &self.save_result { - None => format!( - "Saving up to {} seconds of recent audio", - REPLAY_DURATION.as_secs(), - ), - Some(Ok((path, duration))) => format!( - "Saved {} seconds of all audio to {}", - duration.as_secs(), - path.display(), - ), - Some(Err(e)) => format!("Error saving audio replays: {e:?}"), - }; - - NotificationFrame::new() - .with_title(Some("Saved Audio")) - .show_suppress_button(false) - .on_close(cx.listener(|_, _, _, cx| { - cx.emit(DismissEvent); - })) - .with_content(message) - } - } - - impl CaptureRecentAudioNotification { - fn new(cx: &mut Context) -> Self { - if AudioSettings::get_global(cx).rodio_audio { - let executor = cx.background_executor().clone(); - let save_task = cx.default_global::().save_replays(executor); - let _save_task = cx.spawn(async move |this, cx| { - let res = save_task.await; - this.update(cx, |this, cx| { - this.save_result = Some(res); - cx.notify(); - }) - }); - - Self { - focus_handle: cx.focus_handle(), - _save_task, - save_result: None, - } - } else { - Self { - focus_handle: cx.focus_handle(), - _save_task: Task::ready(Ok(())), - save_result: Some(Err(anyhow::anyhow!( - "Capturing recent audio is only supported on the experimental rodio audio pipeline" - ))), - } - } - } - } - - workspace.show_notification( - NotificationId::unique::(), - cx, - |cx| cx.new(CaptureRecentAudioNotification::new), - ); -} - /// Eagerly loads the active theme and icon theme based on the selections in the /// theme settings. ///