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. ///