audio: Remove rodio audio backend (#51945)

Piotr Osiewicz , Smit Barmase , and Yara created

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

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

<!-- Check before requesting review: -->
- [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 <heysmitbarmase@gmail.com>
Co-authored-by: Yara <git@yara.blue>

Change summary

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 -
crates/livekit_client/src/livekit_client/playback.rs        | 116 ------
crates/livekit_client/src/livekit_client/playback/source.rs |  93 -----
crates/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(-)

Detailed changes

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",
 ]

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.

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
 

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

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<Sound, Buffered<Decoder<Cursor<Vec<u8>>>>>,
-    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<anyhow::Result<(PathBuf, Duration)>> {
-        self.replays.replays_to_tar(executor)
-    }
-
-    pub fn open_microphone(mut voip_parts: VoipParts) -> anyhow::Result<impl Source> {
-        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::<BUFFER_SIZE, _>(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<DeviceId>,
-}
-
-impl VoipParts {
-    pub fn new(cx: &AsyncApp) -> anyhow::Result<Self> {
-        let (apm, replays) = cx.read_default_global::<Audio, _>(|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<DeviceId>,
 ) -> anyhow::Result<rodio::microphone::Microphone> {

crates/audio/src/audio_pipeline/replays.rs 🔗

@@ -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<Mutex<HashMap<String, Replay>>>);
-
-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<anyhow::Result<(PathBuf, Duration)>> {
-        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))
-        })
-    }
-}

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<DeviceId>,
     /// 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),
 };

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<playback::AudioStream> {
-        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<livekit::SessionStats> {

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<Mutex<apm::AudioProcessingModule>>,
@@ -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<AudioStream> {
-    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<AtomicU64>)> {
-        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<TimestampedFrame>, 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;

crates/livekit_client/src/livekit_client/playback/source.rs 🔗

@@ -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<f32> = 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::Item> {
-        self.inner.next()
-    }
-}
-
-impl Source for LiveKitStream {
-    fn current_span_len(&self) -> Option<usize> {
-        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<std::time::Duration> {
-        self.inner.total_duration()
-    }
-}

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<bool>, // 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<bool>,
-    /// 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<bool>,
-    /// 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<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
-    #[serde(rename = "experimental.legacy_audio_compatible")]
-    pub legacy_audio_compatible: Option<bool>,
-    /// Requires 'rodio_audio: true'
-    ///
     /// Select specific output audio device.
     #[serde(rename = "experimental.output_audio_device")]
     pub output_audio_device: Option<AudioOutputDeviceName>,
-    /// Requires 'rodio_audio: true'
-    ///
     /// Select specific input audio device.
     #[serde(rename = "experimental.input_audio_device")]
     pub input_audio_device: Option<AudioInputDeviceName>,

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()],
     }
 }
 

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<Workspace>) {
-    struct CaptureRecentAudioNotification {
-        focus_handle: gpui::FocusHandle,
-        save_result: Option<Result<(PathBuf, Duration), anyhow::Error>>,
-        _save_task: Task<anyhow::Result<()>>,
-    }
-
-    impl gpui::EventEmitter<DismissEvent> for CaptureRecentAudioNotification {}
-    impl gpui::EventEmitter<SuppressEvent> 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<Self>) -> 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>) -> Self {
-            if AudioSettings::get_global(cx).rodio_audio {
-                let executor = cx.background_executor().clone();
-                let save_task = cx.default_global::<audio::Audio>().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::<CaptureRecentAudioNotification>(),
-        cx,
-        |cx| cx.new(CaptureRecentAudioNotification::new),
-    );
-}
-
 /// Eagerly loads the active theme and icon theme based on the selections in the
 /// theme settings.
 ///