Add opus decoder configuration plumbing

- Removes the need for the  OpusGeneratePlc field trial setting
This commit is contained in:
Jim Gustafson 2026-04-06 15:45:35 -07:00
parent a9832d0681
commit fd98a5ca7a
13 changed files with 142 additions and 8 deletions

View File

@ -449,6 +449,9 @@ pub struct AudioConfig {
pub enable_dtx: bool,
/// Flag to enable the Opus in-band FEC.
pub enable_fec: bool,
/// None when using NetEq PLC, 0 use Opus PLC, 5 use Opus Deep PLC (if compiled),
/// 6 use Opus Deep PLC + LACE (if compiled), 7 use Opus Deep PLC + NoLACE (if compiled).
pub decoder_complexity: Option<u8>,
/// Flag to enable transport-wide congestion control for audio.
pub enable_tcc: bool,
/// Flag to enable WebRTC's high pass filter.
@ -511,6 +514,7 @@ impl Default for AudioConfig {
enable_cbr: true,
enable_dtx: true,
enable_fec: true,
decoder_complexity: Some(0),
enable_tcc: false,
enable_high_pass_filter: true,
// Default tests now disable AEC in order to prevent random timing delays

View File

@ -962,6 +962,10 @@ pub async fn start_cli(
args.push(format!("--dtx={}", call_config.audio.enable_dtx));
args.push(format!("--fec={}", call_config.audio.enable_fec));
if let Some(complexity) = call_config.audio.decoder_complexity {
args.push(format!("--decoder-complexity={}", complexity));
}
args.push(format!("--tcc={}", call_config.audio.enable_tcc));
args.push(format!("--vp9={}", call_config.video.enable_vp9));

View File

@ -924,6 +924,66 @@ async fn run_perf_test(test: &mut Test) -> Result<()> {
Ok(())
}
/// Test the different PLC modes: NetEq, Opus.
async fn run_plc_tests(test: &mut Test) -> Result<()> {
let test_cases = [None, Some(0)].map(|decoder_complexity| TestCaseConfig {
test_case_name: format!(
"plc_{}",
decoder_complexity.map_or("None".to_string(), |c| c.to_string())
),
client_a_config: CallConfig {
audio: AudioConfig {
input_name: "normal_phrasing".to_string(),
generate_spectrogram: false,
decoder_complexity,
..Default::default()
},
..Default::default()
},
client_b_config: CallConfig {
audio: AudioConfig {
input_name: "normal_phrasing".to_string(),
visqol_speech_analysis: true,
visqol_audio_analysis: true,
pesq_speech_analysis: true,
plc_speech_analysis: true,
decoder_complexity,
..Default::default()
},
..Default::default()
},
iterations: 1,
..Default::default()
});
test.run(
GroupConfig {
group_name: "plc_tests".to_string(),
summary_report_columns: SummaryReportColumns {
show_visqol_mos_speech: true,
show_visqol_mos_audio: true,
show_pesq_mos: true,
show_plc_mos: true,
show_video: false,
..Default::default()
},
..Default::default()
},
test_cases.into(),
vec![
NetworkProfile::None,
NetworkProfile::SimpleLoss(10),
NetworkProfile::SimpleLoss(20),
NetworkProfile::SimpleLoss(30),
NetworkProfile::SimpleLoss(40),
NetworkProfile::SimpleLoss(50),
],
)
.await?;
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
@ -1007,6 +1067,7 @@ async fn main() -> Result<()> {
}
"changing_bandwidth_audio_test" => run_changing_bandwidth_audio_test(test).await?,
"profiling_suite" => run_perf_test(test).await?,
"plc_tests" => run_plc_tests(test).await?,
_ => panic!("unknown test set \"{test_set_name}\""),
}
test.report().await?;

View File

@ -110,7 +110,6 @@ public class CallManager {
fieldTrialsWithDefaults.put("RingRTC-PruneTurnPorts", "Enabled");
fieldTrialsWithDefaults.put("WebRTC-Bwe-ProbingConfiguration", "skip_if_est_larger_than_fraction_of_max:0.99");
fieldTrialsWithDefaults.put("WebRTC-IncreaseIceCandidatePriorityHostSrflx", "Enabled");
fieldTrialsWithDefaults.put("WebRTC-Audio-OpusGeneratePlc", "Enabled");
fieldTrialsWithDefaults.putAll(fieldTrials);
CallManager.fieldTrials = buildFieldTrialsString(fieldTrialsWithDefaults);

View File

@ -92,7 +92,6 @@ public class CallManagerGlobal {
"RingRTC-PruneTurnPorts": "Enabled",
"WebRTC-Bwe-ProbingConfiguration": "skip_if_est_larger_than_fraction_of_max:0.99",
"WebRTC-IncreaseIceCandidatePriorityHostSrflx": "Enabled",
"WebRTC-Audio-OpusGeneratePlc": "Enabled",
]) { (provided, _) in provided }
RTCInitFieldTrialDictionary(fieldTrialsWithDefaults)
Logger.info("Initialized field trials with \(fieldTrialsWithDefaults)")

View File

@ -46,7 +46,6 @@ class NativeCallManager {
'WebRTC-Bwe-ProbingConfiguration':
'skip_if_est_larger_than_fraction_of_max:0.99',
'WebRTC-IncreaseIceCandidatePriorityHostSrflx': 'Enabled',
'WebRTC-Audio-OpusGeneratePlc': 'Enabled',
},
config.field_trials
);

View File

@ -20,7 +20,7 @@ use ringrtc::{
core::group_call::GroupId,
lite::sfu::{GroupMember, MembershipProof, UserId},
webrtc::{
media::{AudioBandwidth, AudioEncoderConfig},
media::{AudioBandwidth, AudioDecoderConfig, AudioEncoderConfig},
peer_connection_factory::{AudioConfig, AudioJitterBufferConfig, IceServer},
},
};
@ -126,6 +126,10 @@ struct Args {
#[arg(long, default_value_t = 0)]
adaptation: i32,
/// The decoding complexity for audio.
#[arg(long, default_value = None, value_parser = clap::value_parser!(u8).range(0..=10))]
decoder_complexity: Option<u8>,
/// Whether to enable transport-cc feedback for audio. This will allow the bitrate to vary
/// between `min_bitrate_bps` and `max_bitrate_bps` when using CBR.
#[arg(long, action = clap::ArgAction::Set, default_value = "false")]
@ -303,6 +307,9 @@ fn main() -> Result<()> {
enable_fec: args.fec,
dred_duration: 0,
},
audio_decoder_config: AudioDecoderConfig {
complexity: args.decoder_complexity,
},
enable_tcc_audio: args.tcc,
audio_jitter_buffer_config: AudioJitterBufferConfig {
max_packets: args.audio_jitter_buffer_max_packets,

View File

@ -17,7 +17,7 @@ use std::fmt;
pub use versioning::SemanticVersion;
use crate::webrtc::{
media::AudioEncoderConfig,
media::{AudioDecoderConfig, AudioEncoderConfig},
peer_connection_factory::{AudioConfig, AudioJitterBufferConfig},
};
@ -842,6 +842,7 @@ pub struct CallConfig {
pub audio_config: AudioConfig,
pub audio_encoder_config: AudioEncoderConfig,
pub audio_decoder_config: AudioDecoderConfig,
pub enable_tcc_audio: bool,
pub audio_jitter_buffer_config: AudioJitterBufferConfig,
pub audio_rtcp_report_interval_ms: i32,
@ -858,6 +859,7 @@ impl Default for CallConfig {
call_summary_time_limit_secs: 300,
audio_config: Default::default(),
audio_encoder_config: Default::default(),
audio_decoder_config: Default::default(),
enable_tcc_audio: false,
audio_jitter_buffer_config: Default::default(),
audio_rtcp_report_interval_ms: 5000,

View File

@ -763,6 +763,7 @@ where
observer.get_result()?;
peer_connection.configure_audio_encoders(&self.call_config.audio_encoder_config);
peer_connection.configure_audio_decoders(&self.call_config.audio_decoder_config);
self.apply_bandwidth_controller(&mut bandwidth_controller, &mut webrtc)?;
@ -889,6 +890,7 @@ where
observer.get_result()?;
peer_connection.configure_audio_encoders(&self.call_config.audio_encoder_config);
peer_connection.configure_audio_decoders(&self.call_config.audio_decoder_config);
self.apply_bandwidth_controller(&mut bandwidth_controller, &mut webrtc)?;

View File

@ -11,7 +11,7 @@ use crate::{
webrtc,
webrtc::{
ffi::ice_gatherer::RffiIceGatherer,
media::RffiAudioEncoderConfig,
media::{RffiAudioDecoderConfig, RffiAudioEncoderConfig},
network::{RffiIp, RffiIpPort},
peer_connection::{RffiAudioLevel, RffiReceivedAudioLevel},
rtp,
@ -145,6 +145,11 @@ unsafe extern "C" {
config: webrtc::ptr::Borrowed<RffiAudioEncoderConfig>,
);
pub fn Rust_configureAudioDecoders(
peer_connection: webrtc::ptr::BorrowedRc<RffiPeerConnection>,
config: webrtc::ptr::Borrowed<RffiAudioDecoderConfig>,
);
pub fn Rust_getAudioLevels(
peer_connection: webrtc::ptr::BorrowedRc<RffiPeerConnection>,
captured_out: webrtc::ptr::Borrowed<RffiAudioLevel>,

View File

@ -440,3 +440,34 @@ impl AudioEncoderConfig {
}
}
}
// Same as webrtc::AudioDecoder::Config in api/audio_codecs/audio_decoder.h.
// Very OPUS-specific
#[repr(C)]
#[derive(Clone, Debug)]
pub struct RffiAudioDecoderConfig {
complexity: i32,
}
// A nice form of RffiAudioDecoderConfig
#[derive(Clone, Debug)]
pub struct AudioDecoderConfig {
pub complexity: Option<u8>,
}
impl Default for AudioDecoderConfig {
fn default() -> Self {
Self {
complexity: Some(0),
}
}
}
impl AudioDecoderConfig {
pub fn rffi(&self) -> RffiAudioDecoderConfig {
RffiAudioDecoderConfig {
// Set to -1 to use the default Opus value (and avoid setting anything).
complexity: self.complexity.map(|c| c as i32).unwrap_or(-1),
}
}
}

View File

@ -21,7 +21,7 @@ use crate::{
webrtc,
webrtc::{
ice_gatherer::IceGatherer,
media::AudioEncoderConfig,
media::{AudioDecoderConfig, AudioEncoderConfig},
network::RffiIpPort,
peer_connection_factory::RffiPeerConnectionFactoryOwner,
peer_connection_observer::RffiPeerConnectionObserver,
@ -376,6 +376,19 @@ impl PeerConnection {
};
}
pub fn configure_audio_decoders(&self, audio_decoder_config: &AudioDecoderConfig) {
info!(
"PeerConnection.configure_audio_decoders({:?})",
audio_decoder_config
);
unsafe {
pc::Rust_configureAudioDecoders(
self.rffi.as_borrowed(),
webrtc::ptr::Borrowed::from_ptr(&audio_decoder_config.rffi()),
)
};
}
pub fn get_audio_levels(&self) -> (AudioLevel, Vec<RffiReceivedAudioLevel>) {
let captured_level: RffiAudioLevel = 0;
let mut received_levels: Vec<RffiReceivedAudioLevel> = Vec::with_capacity(100);

View File

@ -17,7 +17,7 @@ use crate::{
core::platform::PlatformItem,
webrtc,
webrtc::{
media::RffiAudioEncoderConfig,
media::{RffiAudioDecoderConfig, RffiAudioEncoderConfig},
network::RffiIpPort,
peer_connection::{RffiAudioLevel, RffiReceivedAudioLevel},
rtp,
@ -355,6 +355,14 @@ pub unsafe fn Rust_configureAudioEncoders(
info!("Rust_configureAudioEncoders:");
}
#[allow(non_snake_case, clippy::missing_safety_doc)]
pub unsafe fn Rust_configureAudioDecoders(
_peer_connection: webrtc::ptr::BorrowedRc<RffiPeerConnection>,
_config: webrtc::ptr::Borrowed<RffiAudioDecoderConfig>,
) {
info!("Rust_configureAudioDecoders:");
}
#[allow(non_snake_case, clippy::missing_safety_doc)]
pub fn Rust_getAudioLevels(
_peer_connection: webrtc::ptr::BorrowedRc<RffiPeerConnection>,