Add W12 Opus/network quality profiles
CI / check (push) Failing after 25s
cargo-deny / cargo-deny (push) Has been cancelled
windows-build / windows-build (push) Has been cancelled

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:
2026-06-30 16:13:26 -04:00
co-authored by Claude Opus 4.8
parent 551767f9f5
commit d92d0f6f6b
5 changed files with 271 additions and 4 deletions
+33 -1
View File
@@ -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)