
Rodio parts are well tested and need less configuration then the livekit parts. I suspect there is a bug in the livekit configuration regarding resampling. Rather then investigate that it seemed faster & easier to swap in Rodio. This opens the door to using other Rodio parts like: - Decibel based volume control - Limiter (prevents sound from becoming too loud) - Automatic gain control To use this add to settings: ``` "audio": { "experimental.rodio_audio": true } ``` Release Notes: - N/A Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Antonio Scandurra <me@as-cii.com>
237 lines
6.5 KiB
Rust
237 lines
6.5 KiB
Rust
use anyhow::Context as _;
|
|
use collections::HashMap;
|
|
|
|
mod remote_video_track_view;
|
|
use cpal::traits::HostTrait as _;
|
|
pub use remote_video_track_view::{RemoteVideoTrackView, RemoteVideoTrackViewEvent};
|
|
use rodio::DeviceTrait as _;
|
|
|
|
mod record;
|
|
pub use record::CaptureInput;
|
|
|
|
#[cfg(not(any(
|
|
test,
|
|
feature = "test-support",
|
|
all(target_os = "windows", target_env = "gnu"),
|
|
target_os = "freebsd"
|
|
)))]
|
|
mod livekit_client;
|
|
#[cfg(not(any(
|
|
test,
|
|
feature = "test-support",
|
|
all(target_os = "windows", target_env = "gnu"),
|
|
target_os = "freebsd"
|
|
)))]
|
|
pub use livekit_client::*;
|
|
|
|
// If you need proper LSP in livekit_client you've got to comment
|
|
// - the cfg blocks above
|
|
// - the mods: mock_client & test and their conditional blocks
|
|
// - the pub use mock_client::* and their conditional blocks
|
|
|
|
#[cfg(any(
|
|
test,
|
|
feature = "test-support",
|
|
all(target_os = "windows", target_env = "gnu"),
|
|
target_os = "freebsd"
|
|
))]
|
|
mod mock_client;
|
|
#[cfg(any(
|
|
test,
|
|
feature = "test-support",
|
|
all(target_os = "windows", target_env = "gnu"),
|
|
target_os = "freebsd"
|
|
))]
|
|
pub mod test;
|
|
#[cfg(any(
|
|
test,
|
|
feature = "test-support",
|
|
all(target_os = "windows", target_env = "gnu"),
|
|
target_os = "freebsd"
|
|
))]
|
|
pub use mock_client::*;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Participant {
|
|
Local(LocalParticipant),
|
|
Remote(RemoteParticipant),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum TrackPublication {
|
|
Local(LocalTrackPublication),
|
|
Remote(RemoteTrackPublication),
|
|
}
|
|
|
|
impl TrackPublication {
|
|
pub fn sid(&self) -> TrackSid {
|
|
match self {
|
|
TrackPublication::Local(local) => local.sid(),
|
|
TrackPublication::Remote(remote) => remote.sid(),
|
|
}
|
|
}
|
|
|
|
pub fn is_muted(&self) -> bool {
|
|
match self {
|
|
TrackPublication::Local(local) => local.is_muted(),
|
|
TrackPublication::Remote(remote) => remote.is_muted(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum RemoteTrack {
|
|
Audio(RemoteAudioTrack),
|
|
Video(RemoteVideoTrack),
|
|
}
|
|
|
|
impl RemoteTrack {
|
|
pub fn sid(&self) -> TrackSid {
|
|
match self {
|
|
RemoteTrack::Audio(remote_audio_track) => remote_audio_track.sid(),
|
|
RemoteTrack::Video(remote_video_track) => remote_video_track.sid(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum LocalTrack {
|
|
Audio(LocalAudioTrack),
|
|
Video(LocalVideoTrack),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
#[non_exhaustive]
|
|
pub enum RoomEvent {
|
|
ParticipantConnected(RemoteParticipant),
|
|
ParticipantDisconnected(RemoteParticipant),
|
|
LocalTrackPublished {
|
|
publication: LocalTrackPublication,
|
|
track: LocalTrack,
|
|
participant: LocalParticipant,
|
|
},
|
|
LocalTrackUnpublished {
|
|
publication: LocalTrackPublication,
|
|
participant: LocalParticipant,
|
|
},
|
|
LocalTrackSubscribed {
|
|
track: LocalTrack,
|
|
},
|
|
TrackSubscribed {
|
|
track: RemoteTrack,
|
|
publication: RemoteTrackPublication,
|
|
participant: RemoteParticipant,
|
|
},
|
|
TrackUnsubscribed {
|
|
track: RemoteTrack,
|
|
publication: RemoteTrackPublication,
|
|
participant: RemoteParticipant,
|
|
},
|
|
TrackSubscriptionFailed {
|
|
participant: RemoteParticipant,
|
|
// error: livekit::track::TrackError,
|
|
track_sid: TrackSid,
|
|
},
|
|
TrackPublished {
|
|
publication: RemoteTrackPublication,
|
|
participant: RemoteParticipant,
|
|
},
|
|
TrackUnpublished {
|
|
publication: RemoteTrackPublication,
|
|
participant: RemoteParticipant,
|
|
},
|
|
TrackMuted {
|
|
participant: Participant,
|
|
publication: TrackPublication,
|
|
},
|
|
TrackUnmuted {
|
|
participant: Participant,
|
|
publication: TrackPublication,
|
|
},
|
|
RoomMetadataChanged {
|
|
old_metadata: String,
|
|
metadata: String,
|
|
},
|
|
ParticipantMetadataChanged {
|
|
participant: Participant,
|
|
old_metadata: String,
|
|
metadata: String,
|
|
},
|
|
ParticipantNameChanged {
|
|
participant: Participant,
|
|
old_name: String,
|
|
name: String,
|
|
},
|
|
ParticipantAttributesChanged {
|
|
participant: Participant,
|
|
changed_attributes: HashMap<String, String>,
|
|
},
|
|
ActiveSpeakersChanged {
|
|
speakers: Vec<Participant>,
|
|
},
|
|
ConnectionStateChanged(ConnectionState),
|
|
Connected {
|
|
participants_with_tracks: Vec<(RemoteParticipant, Vec<RemoteTrackPublication>)>,
|
|
},
|
|
Disconnected {
|
|
reason: &'static str,
|
|
},
|
|
Reconnecting,
|
|
Reconnected,
|
|
}
|
|
|
|
pub(crate) fn default_device(
|
|
input: bool,
|
|
) -> anyhow::Result<(cpal::Device, cpal::SupportedStreamConfig)> {
|
|
let device;
|
|
let config;
|
|
if input {
|
|
device = cpal::default_host()
|
|
.default_input_device()
|
|
.context("no audio input device available")?;
|
|
config = device
|
|
.default_input_config()
|
|
.context("failed to get default input config")?;
|
|
} else {
|
|
device = cpal::default_host()
|
|
.default_output_device()
|
|
.context("no audio output device available")?;
|
|
config = device
|
|
.default_output_config()
|
|
.context("failed to get default output config")?;
|
|
}
|
|
Ok((device, config))
|
|
}
|
|
|
|
pub(crate) fn get_sample_data(
|
|
sample_format: cpal::SampleFormat,
|
|
data: &cpal::Data,
|
|
) -> anyhow::Result<Vec<i16>> {
|
|
match sample_format {
|
|
cpal::SampleFormat::I8 => Ok(convert_sample_data::<i8, i16>(data)),
|
|
cpal::SampleFormat::I16 => Ok(data.as_slice::<i16>().unwrap().to_vec()),
|
|
cpal::SampleFormat::I24 => Ok(convert_sample_data::<cpal::I24, i16>(data)),
|
|
cpal::SampleFormat::I32 => Ok(convert_sample_data::<i32, i16>(data)),
|
|
cpal::SampleFormat::I64 => Ok(convert_sample_data::<i64, i16>(data)),
|
|
cpal::SampleFormat::U8 => Ok(convert_sample_data::<u8, i16>(data)),
|
|
cpal::SampleFormat::U16 => Ok(convert_sample_data::<u16, i16>(data)),
|
|
cpal::SampleFormat::U32 => Ok(convert_sample_data::<u32, i16>(data)),
|
|
cpal::SampleFormat::U64 => Ok(convert_sample_data::<u64, i16>(data)),
|
|
cpal::SampleFormat::F32 => Ok(convert_sample_data::<f32, i16>(data)),
|
|
cpal::SampleFormat::F64 => Ok(convert_sample_data::<f64, i16>(data)),
|
|
_ => anyhow::bail!("Unsupported sample format"),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn convert_sample_data<
|
|
TSource: cpal::SizedSample,
|
|
TDest: cpal::SizedSample + cpal::FromSample<TSource>,
|
|
>(
|
|
data: &cpal::Data,
|
|
) -> Vec<TDest> {
|
|
data.as_slice::<TSource>()
|
|
.unwrap()
|
|
.iter()
|
|
.map(|e| e.to_sample::<TDest>())
|
|
.collect()
|
|
}
|