Add W12 Opus/network quality profiles
Add a small, named codec-policy picker (Low latency / Balanced / Bad network) instead of exposing raw Opus knobs. The profile->params mapping is a pure function (`codec::opus_impl::opus_params`) for unit testing; profiles tune bitrate, in-band FEC, expected packet-loss, and DTX. - config: `AudioProfile` enum (serde + Display + ALL + u8 round-trip), persisted `audio_profile` field (default Balanced). - codec: `OpusParams` + pure `opus_params()` + `OpusEncoder::apply_params` / `apply_profile`. - core: new `SetAudioProfile` command (Reliable, no coalesce); a shared `AtomicU8` lets the capture thread re-tune the live encoder on a mid-call switch and read it at each new call's encoder creation. - app: Settings "Connection quality" picker in the Audio tab, startup config-sync send, and a one-line hint per profile. No wire-format change (GOSSIP/audio planes untouched). 499 lib tests green (config + codec mapping/apply tests added), clippy + fmt clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+33
-1
@@ -4,7 +4,7 @@ use crate::audio::clip_player::{
|
||||
};
|
||||
use crate::audio::eq::{EQ_GAIN_DB_MAX, EQ_GAIN_DB_MIN, EqSettings};
|
||||
use crate::audio::{AudioDevice, enumerate_audio_devices};
|
||||
use crate::config::{AppConfig, NetworkMode, RecordingMode, RoomLayout};
|
||||
use crate::config::{AppConfig, AudioProfile, NetworkMode, RecordingMode, RoomLayout};
|
||||
use crate::core::{
|
||||
CoreController,
|
||||
messages::{CoreCommand, UiEvent},
|
||||
@@ -377,6 +377,7 @@ pub enum AppMessage {
|
||||
/// immediately but does not persist (saved once on release via NoiseGateChanged).
|
||||
NoiseGateDragging(f32),
|
||||
NetworkModeSelected(NetworkMode),
|
||||
AudioProfileSelected(AudioProfile),
|
||||
RecordingModeSelected(RecordingMode),
|
||||
/// Choose the friends presence posture (W7): invisible / normal / discoverable.
|
||||
PresenceModeSelected(PresenceMode),
|
||||
@@ -919,6 +920,7 @@ impl Default for AppState {
|
||||
let _ = controller.send(CoreCommand::SetInputVolume(config.input_volume));
|
||||
let _ = controller.send(CoreCommand::SetOutputVolume(config.output_volume));
|
||||
let _ = controller.send(CoreCommand::SetNetworkMode(config.network_mode));
|
||||
let _ = controller.send(CoreCommand::SetAudioProfile(config.audio_profile));
|
||||
let _ = controller.send(CoreCommand::SetRecordingMode(config.recording_mode));
|
||||
let _ = controller.send(CoreCommand::SetPixelpassPath(config.pixelpass_path.clone()));
|
||||
let _ = controller.send(CoreCommand::SetPresenceMode(config.presence_mode));
|
||||
@@ -2050,6 +2052,12 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
|
||||
// Applied on the next join, since the endpoint is rebuilt then.
|
||||
let _ = state.controller.send(CoreCommand::SetNetworkMode(mode));
|
||||
}
|
||||
AppMessage::AudioProfileSelected(profile) => {
|
||||
state.config.audio_profile = profile;
|
||||
state.config.save();
|
||||
// Applies live to the running encoder, and to the next call.
|
||||
let _ = state.controller.send(CoreCommand::SetAudioProfile(profile));
|
||||
}
|
||||
AppMessage::RecordingModeSelected(mode) => {
|
||||
state.config.recording_mode = mode;
|
||||
state.config.save();
|
||||
@@ -3123,6 +3131,19 @@ fn network_mode_hint(mode: NetworkMode) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// One-line explanation of an audio/network profile for the settings picker (W12).
|
||||
fn audio_profile_hint(profile: AudioProfile) -> &'static str {
|
||||
match profile {
|
||||
AudioProfile::LowLatency => {
|
||||
"Lowest delay, no loss recovery. Best on a clean LAN or wired link."
|
||||
}
|
||||
AudioProfile::Balanced => "Default: voice quality with light loss recovery.",
|
||||
AudioProfile::BadNetwork => {
|
||||
"Most resilient on a lossy/congested link: extra loss recovery, lower bitrate."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One-line explanation of a recording mode for the settings picker.
|
||||
fn recording_mode_hint(mode: RecordingMode) -> &'static str {
|
||||
match mode {
|
||||
@@ -4981,6 +5002,17 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
control
|
||||
},
|
||||
].spacing(8).width(iced::Length::Fill),
|
||||
vertical_space(section_gap),
|
||||
section_header("Connection quality"),
|
||||
column![
|
||||
pick_list(
|
||||
&AudioProfile::ALL[..],
|
||||
Some(state.config.audio_profile),
|
||||
AppMessage::AudioProfileSelected,
|
||||
).width(iced::Length::Fill),
|
||||
text(audio_profile_hint(state.config.audio_profile)).size(11).color(color_subtext),
|
||||
text("Applies immediately, even mid-call.").size(11).color(color_subtext),
|
||||
].spacing(4).width(iced::Length::Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
|
||||
Reference in New Issue
Block a user