Merge pull request #39 from EdisonJwa/refactor/remove-ios-raw-unit
refactor(audio): remove experimental iOS RemoteIO+WebRTC APM path
This commit is contained in:
@@ -23,7 +23,7 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/haptic_kit/ios"
|
:path: ".symlinks/plugins/haptic_kit/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
chanora_bridge: 26252acdf9ca660ce9c132ad25cd5ad5af467b16
|
chanora_bridge: 27a03592058709f6f38701343eb51c3a55b02da0
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_foreground_task: a159d2c2173b33699ddb3e6c2a067045d7cebb89
|
flutter_foreground_task: a159d2c2173b33699ddb3e6c2a067045d7cebb89
|
||||||
haptic_kit: b22c4fbb2aa7b0d66f2891f81a9e950ad2de5758
|
haptic_kit: b22c4fbb2aa7b0d66f2891f81a9e950ad2de5758
|
||||||
|
|||||||
@@ -1339,11 +1339,8 @@ sealed class BridgeEvent with _$BridgeEvent {
|
|||||||
|
|
||||||
/// Bridge iOS voice-processing mode.
|
/// Bridge iOS voice-processing mode.
|
||||||
enum BridgeIosVoiceProcessingMode {
|
enum BridgeIosVoiceProcessingMode {
|
||||||
/// Shipping VPIO path.
|
/// Apple VoiceProcessingIO path.
|
||||||
platformVoiceProcessing,
|
platformVoiceProcessing,
|
||||||
|
|
||||||
/// Experimental Sonora path.
|
|
||||||
sonoraExperimental,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
|||||||
@@ -56,10 +56,8 @@ impl AudioRoute {
|
|||||||
/// iOS voice-processing mode.
|
/// iOS voice-processing mode.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum IosVoiceProcessingMode {
|
pub enum IosVoiceProcessingMode {
|
||||||
/// Shipping default: Apple VoiceProcessingIO owns AEC/NS/AGC.
|
/// Apple VoiceProcessingIO owns AEC/NS/AGC.
|
||||||
PlatformVoiceProcessing,
|
PlatformVoiceProcessing,
|
||||||
/// Experimental raw capture-processing path.
|
|
||||||
SonoraExperimental,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processing backend selected by policy/config.
|
/// Processing backend selected by policy/config.
|
||||||
@@ -195,28 +193,20 @@ impl AudioProcessingConfig {
|
|||||||
"bluetooth_a2dp is output-only and cannot transmit duplex voice".to_string(),
|
"bluetooth_a2dp is output-only and cannot transmit duplex voice".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if self.ios_mode == IosVoiceProcessingMode::PlatformVoiceProcessing
|
if self.processing_backend == AudioBackend::Sonora
|
||||||
&& (self.processing_backend == AudioBackend::Sonora
|
|
||||||
|| self.processing_backend == AudioBackend::WebrtcApm
|
|| self.processing_backend == AudioBackend::WebrtcApm
|
||||||
|| self.aec == EffectOwner::Sonora
|
|| self.aec == EffectOwner::Sonora
|
||||||
|| self.aec == EffectOwner::WebrtcApm
|
|| self.aec == EffectOwner::WebrtcApm
|
||||||
|| self.ns == EffectOwner::Sonora
|
|| self.ns == EffectOwner::Sonora
|
||||||
|| self.ns == EffectOwner::WebrtcApm
|
|| self.ns == EffectOwner::WebrtcApm
|
||||||
|| self.agc == EffectOwner::Sonora
|
|| self.agc == EffectOwner::Sonora
|
||||||
|| self.agc == EffectOwner::WebrtcApm)
|
|| self.agc == EffectOwner::WebrtcApm
|
||||||
{
|
{
|
||||||
return Err(AudioError::InvalidAudioProcessingConfig(
|
return Err(AudioError::InvalidAudioProcessingConfig(
|
||||||
"software audio processing cannot be enabled with iOS VoiceProcessingIO"
|
"software audio processing cannot be enabled with iOS VoiceProcessingIO"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if self.ios_mode == IosVoiceProcessingMode::SonoraExperimental
|
|
||||||
&& self.processing_backend != AudioBackend::WebrtcApm
|
|
||||||
{
|
|
||||||
return Err(AudioError::InvalidAudioProcessingConfig(
|
|
||||||
"ios raw processing mode requires the WebRTC APM backend".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,34 +252,6 @@ mod tests {
|
|||||||
assert!(config.validate_for_ios().is_err());
|
assert!(config.validate_for_ios().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn raw_processing_allows_full_webrtc_apm_chain() {
|
|
||||||
let config = AudioProcessingConfig {
|
|
||||||
ios_mode: IosVoiceProcessingMode::SonoraExperimental,
|
|
||||||
processing_backend: AudioBackend::WebrtcApm,
|
|
||||||
aec: EffectOwner::WebrtcApm,
|
|
||||||
ns: EffectOwner::WebrtcApm,
|
|
||||||
agc: EffectOwner::WebrtcApm,
|
|
||||||
..AudioProcessingConfig::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(config.validate_for_ios().is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn raw_processing_rejects_non_webrtc_apm_backend() {
|
|
||||||
let config = AudioProcessingConfig {
|
|
||||||
ios_mode: IosVoiceProcessingMode::SonoraExperimental,
|
|
||||||
processing_backend: AudioBackend::PlatformVoiceProcessing,
|
|
||||||
aec: EffectOwner::WebrtcApm,
|
|
||||||
ns: EffectOwner::WebrtcApm,
|
|
||||||
agc: EffectOwner::WebrtcApm,
|
|
||||||
..AudioProcessingConfig::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(config.validate_for_ios().is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn disable_failed_vad_backend_demotes_to_webrtc() {
|
fn disable_failed_vad_backend_demotes_to_webrtc() {
|
||||||
let mut config = AudioProcessingConfig {
|
let mut config = AudioProcessingConfig {
|
||||||
|
|||||||
@@ -314,15 +314,6 @@ mod tests {
|
|||||||
rec.stop();
|
rec.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ios_raw_debug_wav_does_not_push_from_realtime_callback() {
|
|
||||||
let src = include_str!("ios_raw_unit.rs");
|
|
||||||
assert!(
|
|
||||||
!src.contains("push_raw_mic") && !src.contains("push_processed_mic"),
|
|
||||||
"ios raw callbacks must not call WavDebugRecorder push_*_mic until it has a preallocated handoff"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wav_header_is_44_bytes() {
|
fn wav_header_is_44_bytes() {
|
||||||
// Write to a temp file to test the header.
|
// Write to a temp file to test the header.
|
||||||
|
|||||||
@@ -395,8 +395,6 @@ unsafe impl Sync for AudioEngine {}
|
|||||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||||
enum IosVoiceBackend {
|
enum IosVoiceBackend {
|
||||||
Vpio(crate::ios_voice_unit::IosVoiceUnit),
|
Vpio(crate::ios_voice_unit::IosVoiceUnit),
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
Raw(crate::ios_raw_unit::IosRawUnit),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||||
@@ -406,14 +404,12 @@ impl IosVoiceBackend {
|
|||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Self::Vpio(unit) => unit.restart(),
|
Self::Vpio(unit) => unit.restart(),
|
||||||
Self::Raw(unit) => unit.restart(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
match self {
|
let _ = self;
|
||||||
Self::Vpio(_unit) => Ok(()),
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,14 +418,12 @@ impl IosVoiceBackend {
|
|||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Self::Vpio(unit) => unit.pause(),
|
Self::Vpio(unit) => unit.pause(),
|
||||||
Self::Raw(unit) => unit.pause(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
match self {
|
let _ = self;
|
||||||
Self::Vpio(_unit) => Ok(()),
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,14 +432,12 @@ impl IosVoiceBackend {
|
|||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Self::Vpio(unit) => unit.resume(),
|
Self::Vpio(unit) => unit.resume(),
|
||||||
Self::Raw(unit) => unit.resume(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
match self {
|
let _ = self;
|
||||||
Self::Vpio(_unit) => Ok(()),
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,27 +446,6 @@ impl IosVoiceBackend {
|
|||||||
fn open_ios_voice_backend(
|
fn open_ios_voice_backend(
|
||||||
params: crate::mobile_voice_backend::VoiceAudioParams,
|
params: crate::mobile_voice_backend::VoiceAudioParams,
|
||||||
) -> Result<IosVoiceBackend, AudioError> {
|
) -> Result<IosVoiceBackend, AudioError> {
|
||||||
let _cfg = params.audio_processing_config.lock().unwrap().clone();
|
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
{
|
|
||||||
if _cfg.ios_mode == crate::IosVoiceProcessingMode::SonoraExperimental {
|
|
||||||
let raw_params = params.clone();
|
|
||||||
match crate::ios_raw_unit::IosRawUnit::start(raw_params) {
|
|
||||||
Ok(unit) => {
|
|
||||||
info!(target: "chanora_audio", "ios: RemoteIO/WebRTC APM backend selected");
|
|
||||||
return Ok(IosVoiceBackend::Raw(unit));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(
|
|
||||||
target: "chanora_audio",
|
|
||||||
error = %e,
|
|
||||||
"ios: RemoteIO/WebRTC APM backend failed; falling back to VoiceProcessingIO"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let unit = crate::ios_voice_unit::IosVoiceUnit::start(params)?;
|
let unit = crate::ios_voice_unit::IosVoiceUnit::start(params)?;
|
||||||
Ok(IosVoiceBackend::Vpio(unit))
|
Ok(IosVoiceBackend::Vpio(unit))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,538 +0,0 @@
|
|||||||
//! Optional raw iOS RemoteIO path for the WebRTC APM experimental mode.
|
|
||||||
//!
|
|
||||||
//! Provides an alternative to `ios_voice_unit.rs` for the
|
|
||||||
//! `SonoraExperimental` processing mode. Instead of
|
|
||||||
//! `kAudioUnitSubType_VoiceProcessingIO` (which owns AEC/NS/AGC), it
|
|
||||||
//! opens `kAudioUnitSubType_RemoteIO` with voice processing explicitly
|
|
||||||
//! disabled so WebRTC APM can own the full signal path.
|
|
||||||
//!
|
|
||||||
//! ## Hard invariants enforced here
|
|
||||||
//!
|
|
||||||
//! * INV_009: Rust AEC only active when platform AEC is disabled.
|
|
||||||
//! * INV_010: VoiceProcessingIO and WebRTC APM AEC are mutually exclusive.
|
|
||||||
//! * INV_011: Software AEC backend receives both capture and render-reference.
|
|
||||||
//! * INV_012: Render reference is copied from decoded/mixed remote PCM
|
|
||||||
//! before playout.
|
|
||||||
//!
|
|
||||||
//! ## Fallback
|
|
||||||
//!
|
|
||||||
//! If RemoteIO construction fails, the caller falls back to `IosVoiceUnit`
|
|
||||||
//! (VPIO) and logs the error.
|
|
||||||
//!
|
|
||||||
//! ## Status
|
|
||||||
//!
|
|
||||||
//! Experimental / disabled by default. Only activated when the user
|
|
||||||
//! explicitly selects `SonoraExperimental` mode via the bridge API.
|
|
||||||
//!
|
|
||||||
//! ## Platform
|
|
||||||
//!
|
|
||||||
//! `kAudioUnitSubType_RemoteIO` is only available in the iOS SDK.
|
|
||||||
//! This module is gated to `target_os = "ios"`.
|
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
pub use inner::IosRawUnit;
|
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
mod inner {
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use audiopus::coder::Encoder as OpusEncoder;
|
|
||||||
use coreaudio::audio_unit::audio_format::LinearPcmFlags;
|
|
||||||
use coreaudio::audio_unit::render_callback::{self, data};
|
|
||||||
use coreaudio::audio_unit::IOType;
|
|
||||||
use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat};
|
|
||||||
use tracing::{info, warn};
|
|
||||||
|
|
||||||
use crate::mobile_voice_backend::VoiceAudioParams;
|
|
||||||
use crate::processor::AudioProcessor;
|
|
||||||
use crate::AudioError;
|
|
||||||
|
|
||||||
const SAMPLE_RATE_HZ: f64 = 48_000.0;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
// Render-reference ring buffer //
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
|
|
||||||
/// 4-slot ring buffer shared between the render callback (writer) and
|
|
||||||
/// the capture callback (reader for Sonora AEC3). Capacity: 4 × 10 ms
|
|
||||||
/// = 40 ms of headroom.
|
|
||||||
///
|
|
||||||
/// If the capture callback runs before the render callback has written
|
|
||||||
/// a frame it reads zeros (silence reference), which is safe — Sonora
|
|
||||||
/// AEC3 simply skips cancellation for that frame.
|
|
||||||
type RenderReferenceBuffer = crate::render_reference::RenderReferenceBuffer<480, 4>;
|
|
||||||
type RenderReferenceFrameAccumulator =
|
|
||||||
crate::render_reference::RenderReferenceFrameAccumulator<480>;
|
|
||||||
const RAW_RENDER_SCRATCH_FRAMES: usize = 1024;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
// Capture pipeline state //
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
|
|
||||||
struct RawCaptureState {
|
|
||||||
encoder: OpusEncoder,
|
|
||||||
pcm_accum: Vec<i16>,
|
|
||||||
opus_out: [u8; crate::opus_voice::MAX_OPUS_FRAME],
|
|
||||||
voice_out_tx: crate::opus_voice::EncodedVoiceFrameSender,
|
|
||||||
transmit_active: Arc<AtomicBool>,
|
|
||||||
output_muted: Arc<AtomicBool>,
|
|
||||||
mic_gain: f32,
|
|
||||||
voice_activity_selector: Option<Arc<crate::TransmitModeSelector>>,
|
|
||||||
vad_detector: crate::vad::WebRtcFallbackVad,
|
|
||||||
silero_coreml_worker: Option<crate::vad::apple_coreml::AppleCoreMlVadWorker>,
|
|
||||||
current_vad_backend: crate::VadBackend,
|
|
||||||
capture_frame_seq: u64,
|
|
||||||
vad_state: crate::voice_activity::VoiceActivityStateMachine,
|
|
||||||
/// Processing config — retained for route-change reloads.
|
|
||||||
audio_processing_config: Arc<Mutex<crate::AudioProcessingConfig>>,
|
|
||||||
webrtc_apm_processor: crate::processor::WebRtcApmProcessor,
|
|
||||||
audio_processing_stats: Arc<crate::SharedAudioProcessingStats>,
|
|
||||||
render_reference: Arc<RenderReferenceBuffer>,
|
|
||||||
pending_10ms: [i16; crate::frame::FRAME_10MS_SAMPLES],
|
|
||||||
pending_10ms_len: usize,
|
|
||||||
fallback_warned_backend: Option<crate::VadBackend>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawCaptureState {
|
|
||||||
fn new(
|
|
||||||
params: &VoiceAudioParams,
|
|
||||||
render_reference: Arc<RenderReferenceBuffer>,
|
|
||||||
) -> Result<Self, AudioError> {
|
|
||||||
let encoder = crate::opus_voice::new_voip_encoder("ios raw")?;
|
|
||||||
let webrtc_apm_config = params
|
|
||||||
.audio_processing_config
|
|
||||||
.lock()
|
|
||||||
.map(|cfg| crate::processor::webrtc_apm::WebRtcApmConfig::from_audio_config(&cfg))
|
|
||||||
.unwrap_or_default();
|
|
||||||
Ok(Self {
|
|
||||||
encoder,
|
|
||||||
pcm_accum: Vec::with_capacity(crate::frame::FRAME_20MS_SAMPLES * 2),
|
|
||||||
opus_out: [0u8; crate::opus_voice::MAX_OPUS_FRAME],
|
|
||||||
voice_out_tx: crate::opus_voice::start_out_packet_worker(
|
|
||||||
params.voice_out_tx.clone(),
|
|
||||||
params.frames_sent.clone(),
|
|
||||||
"ios-raw",
|
|
||||||
)?,
|
|
||||||
transmit_active: params.transmit_active.clone(),
|
|
||||||
output_muted: params.output_muted.clone(),
|
|
||||||
mic_gain: params.mic_gain,
|
|
||||||
voice_activity_selector: params.voice_activity_selector.clone(),
|
|
||||||
vad_detector: crate::vad::WebRtcFallbackVad::default(),
|
|
||||||
silero_coreml_worker: None,
|
|
||||||
current_vad_backend: crate::VadBackend::WebrtcVad,
|
|
||||||
capture_frame_seq: 0,
|
|
||||||
vad_state: crate::voice_activity::VoiceActivityStateMachine::default(),
|
|
||||||
audio_processing_config: params.audio_processing_config.clone(),
|
|
||||||
webrtc_apm_processor: crate::processor::WebRtcApmProcessor::with_config(
|
|
||||||
webrtc_apm_config,
|
|
||||||
)?,
|
|
||||||
audio_processing_stats: params.audio_processing_stats.clone(),
|
|
||||||
render_reference,
|
|
||||||
pending_10ms: [0_i16; crate::frame::FRAME_10MS_SAMPLES],
|
|
||||||
pending_10ms_len: 0,
|
|
||||||
fallback_warned_backend: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mark_vad_fallback_active(&mut self, failed_backend: crate::VadBackend) {
|
|
||||||
if self.fallback_warned_backend != Some(failed_backend) {
|
|
||||||
self.fallback_warned_backend = Some(failed_backend);
|
|
||||||
if self.capture_frame_seq < 128 {
|
|
||||||
tracing::info!(
|
|
||||||
target: "chanora_audio",
|
|
||||||
backend = failed_backend.as_str(),
|
|
||||||
seq = self.capture_frame_seq,
|
|
||||||
"VAD backend warming up; using WebRTC fallback"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tracing::warn!(
|
|
||||||
target: "chanora_audio",
|
|
||||||
backend = failed_backend.as_str(),
|
|
||||||
"VAD backend unavailable; using WebRTC fallback for runtime detection"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ingest_i16(&mut self, samples: &[i16]) {
|
|
||||||
// Accumulate into 10 ms frames for VAD / Sonora processing.
|
|
||||||
let mut offset = 0;
|
|
||||||
while offset < samples.len() {
|
|
||||||
let remaining = crate::frame::FRAME_10MS_SAMPLES - self.pending_10ms_len;
|
|
||||||
let take = remaining.min(samples.len() - offset);
|
|
||||||
self.pending_10ms[self.pending_10ms_len..self.pending_10ms_len + take]
|
|
||||||
.copy_from_slice(&samples[offset..offset + take]);
|
|
||||||
self.pending_10ms_len += take;
|
|
||||||
offset += take;
|
|
||||||
|
|
||||||
if self.pending_10ms_len == crate::frame::FRAME_10MS_SAMPLES {
|
|
||||||
let frame = self.pending_10ms;
|
|
||||||
self.process_10ms_capture_frame(&frame);
|
|
||||||
self.encode_complete_20ms_frames();
|
|
||||||
self.pending_10ms_len = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.transmit_active.load(Ordering::Relaxed) {
|
|
||||||
self.pcm_accum.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.encode_complete_20ms_frames();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encode_complete_20ms_frames(&mut self) {
|
|
||||||
while self.pcm_accum.len() >= crate::frame::FRAME_20MS_SAMPLES {
|
|
||||||
let mut frame = [0i16; crate::frame::FRAME_20MS_SAMPLES];
|
|
||||||
frame.copy_from_slice(&self.pcm_accum[..crate::frame::FRAME_20MS_SAMPLES]);
|
|
||||||
self.pcm_accum.drain(..crate::frame::FRAME_20MS_SAMPLES);
|
|
||||||
|
|
||||||
match self.encoder.encode(&frame, &mut self.opus_out[..]) {
|
|
||||||
Ok(len) => {
|
|
||||||
crate::opus_voice::send_voip_frame(
|
|
||||||
&self.voice_out_tx,
|
|
||||||
&self.opus_out,
|
|
||||||
len,
|
|
||||||
|| {
|
|
||||||
warn!(
|
|
||||||
target: "chanora_audio",
|
|
||||||
"ios raw: voice_out queue full; dropping frame"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|| {},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(target: "chanora_audio",
|
|
||||||
error = %e, "ios raw opus encode failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_10ms_capture_frame(
|
|
||||||
&mut self,
|
|
||||||
samples: &[i16; crate::frame::FRAME_10MS_SAMPLES],
|
|
||||||
) {
|
|
||||||
let mut frame = [0.0_f32; crate::frame::FRAME_10MS_SAMPLES];
|
|
||||||
for (dst, src) in frame.iter_mut().zip(samples.iter().copied()) {
|
|
||||||
*dst = crate::frame::i16_to_f32(src);
|
|
||||||
}
|
|
||||||
let input_dbfs = crate::frame::dbfs(&frame);
|
|
||||||
|
|
||||||
// Debug WAV mic taps are intentionally unavailable on iOS raw
|
|
||||||
// realtime callbacks until WavDebugRecorder supports a
|
|
||||||
// preallocated handoff path; the current recorder push path
|
|
||||||
// allocates per frame.
|
|
||||||
|
|
||||||
// Feed render reference to WebRTC APM before capture so AEC can adapt.
|
|
||||||
let render_ref = self.render_reference.read_latest();
|
|
||||||
self.webrtc_apm_processor.process_render(&render_ref);
|
|
||||||
self.webrtc_apm_processor.process_capture(&mut frame);
|
|
||||||
|
|
||||||
// Processed-mic debug WAV capture is disabled for the same
|
|
||||||
// realtime allocation reason as the raw-mic tap above.
|
|
||||||
|
|
||||||
let voice_activity_mode = self
|
|
||||||
.voice_activity_selector
|
|
||||||
.as_ref()
|
|
||||||
.map(|selector| selector.mode() == crate::TransmitMode::VoiceActivity)
|
|
||||||
.unwrap_or(false);
|
|
||||||
if !voice_activity_mode {
|
|
||||||
self.silero_coreml_worker = None;
|
|
||||||
self.current_vad_backend = crate::VadBackend::Disabled;
|
|
||||||
self.fallback_warned_backend = None;
|
|
||||||
self.audio_processing_stats.set_vad_fallback_active(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (vad_backend, vad_hangover) = self
|
|
||||||
.audio_processing_config
|
|
||||||
.try_lock()
|
|
||||||
.map(|cfg| (cfg.vad_backend, cfg.vad_hangover_ms))
|
|
||||||
.unwrap_or((
|
|
||||||
crate::VadBackend::WebrtcVad,
|
|
||||||
crate::voice_activity::VAD_HANGOVER_MS,
|
|
||||||
));
|
|
||||||
if voice_activity_mode {
|
|
||||||
self.vad_state.configure(
|
|
||||||
crate::voice_activity::VAD_OPEN_AFTER_MS,
|
|
||||||
vad_hangover,
|
|
||||||
crate::voice_activity::VAD_MIN_TX_MS,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if voice_activity_mode && vad_backend != self.current_vad_backend {
|
|
||||||
self.current_vad_backend = vad_backend;
|
|
||||||
self.fallback_warned_backend = None;
|
|
||||||
if vad_backend == crate::VadBackend::SileroOnnx {
|
|
||||||
self.silero_coreml_worker = None;
|
|
||||||
self.mark_vad_fallback_active(crate::VadBackend::SileroOnnx);
|
|
||||||
self.audio_processing_stats.set_vad_fallback_active(true);
|
|
||||||
} else {
|
|
||||||
self.silero_coreml_worker = None;
|
|
||||||
self.audio_processing_stats.set_vad_fallback_active(false);
|
|
||||||
}
|
|
||||||
self.vad_state.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (vad_probability, active) = if voice_activity_mode {
|
|
||||||
self.capture_frame_seq = self.capture_frame_seq.wrapping_add(1);
|
|
||||||
let capture_seq = self.capture_frame_seq;
|
|
||||||
let mut used_fallback_vad = false;
|
|
||||||
let vad = if vad_backend == crate::VadBackend::Disabled {
|
|
||||||
crate::vad::VadOutput {
|
|
||||||
probability: 1.0,
|
|
||||||
speech: true,
|
|
||||||
}
|
|
||||||
} else if vad_backend == crate::VadBackend::SileroOnnx {
|
|
||||||
match crate::vad::callback_vad_worker_policy(
|
|
||||||
voice_activity_mode,
|
|
||||||
vad_backend,
|
|
||||||
self.silero_coreml_worker.is_some(),
|
|
||||||
) {
|
|
||||||
crate::vad::VadWorkerPolicy::UseWorker => {
|
|
||||||
let worker = self
|
|
||||||
.silero_coreml_worker
|
|
||||||
.as_ref()
|
|
||||||
.expect("policy checked worker");
|
|
||||||
let enqueued = worker.try_send(capture_seq, &frame);
|
|
||||||
if !worker.is_stale(capture_seq) {
|
|
||||||
let p = worker.latest_probability();
|
|
||||||
crate::vad::VadOutput {
|
|
||||||
probability: p,
|
|
||||||
speech: p >= 0.5,
|
|
||||||
}
|
|
||||||
} else if enqueued {
|
|
||||||
crate::vad::VadOutput {
|
|
||||||
probability: 0.0,
|
|
||||||
speech: false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
used_fallback_vad = true;
|
|
||||||
self.mark_vad_fallback_active(vad_backend);
|
|
||||||
crate::vad::VoiceActivityDetector::process_10ms(
|
|
||||||
&mut self.vad_detector,
|
|
||||||
&frame,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crate::vad::VadWorkerPolicy::UseFallback => {
|
|
||||||
used_fallback_vad = true;
|
|
||||||
self.mark_vad_fallback_active(vad_backend);
|
|
||||||
crate::vad::VoiceActivityDetector::process_10ms(
|
|
||||||
&mut self.vad_detector,
|
|
||||||
&frame,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
crate::vad::VadWorkerPolicy::NotModelBacked => crate::vad::VadOutput {
|
|
||||||
probability: 1.0,
|
|
||||||
speech: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
crate::vad::VoiceActivityDetector::process_10ms(&mut self.vad_detector, &frame)
|
|
||||||
};
|
|
||||||
self.audio_processing_stats
|
|
||||||
.set_vad_fallback_active(used_fallback_vad);
|
|
||||||
(vad.probability, self.vad_state.update(vad.speech))
|
|
||||||
} else {
|
|
||||||
(0.0, false)
|
|
||||||
};
|
|
||||||
if let Some(sel) = &self.voice_activity_selector {
|
|
||||||
sel.set_voice_activity_open(voice_activity_mode && active);
|
|
||||||
}
|
|
||||||
self.audio_processing_stats.update_capture(
|
|
||||||
input_dbfs,
|
|
||||||
crate::frame::dbfs(&frame),
|
|
||||||
vad_probability,
|
|
||||||
voice_activity_mode && active,
|
|
||||||
self.transmit_active.load(Ordering::Relaxed),
|
|
||||||
);
|
|
||||||
|
|
||||||
if !self.transmit_active.load(Ordering::Relaxed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if crate::capture_accumulator::append_processed_i16_bounded(
|
|
||||||
&mut self.pcm_accum,
|
|
||||||
&frame,
|
|
||||||
self.mic_gain,
|
|
||||||
) {
|
|
||||||
self.audio_processing_stats.increment_callback_xrun();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
// IosRawUnit //
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
|
|
||||||
/// Raw iOS RemoteIO audio unit for the Sonora experimental path.
|
|
||||||
pub struct IosRawUnit {
|
|
||||||
unit: AudioUnit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IosRawUnit {
|
|
||||||
/// Open a RemoteIO AudioUnit, install render + input callbacks, start.
|
|
||||||
pub(crate) fn start(params: VoiceAudioParams) -> Result<Self, AudioError> {
|
|
||||||
// INV_010: reject if config requests VPIO (that's IosVoiceUnit's job).
|
|
||||||
{
|
|
||||||
let cfg = params.audio_processing_config.lock().unwrap();
|
|
||||||
if cfg.ios_mode == crate::IosVoiceProcessingMode::PlatformVoiceProcessing {
|
|
||||||
return Err(AudioError::InvalidAudioProcessingConfig(
|
|
||||||
"IosRawUnit requires raw WebRTC APM mode".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut unit = AudioUnit::new_uninitialized(IOType::RemoteIO)
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio new: {e}")))?;
|
|
||||||
|
|
||||||
// Enable input on bus 1.
|
|
||||||
const ENABLE_IO: u32 = 2003;
|
|
||||||
let enable: u32 = 1;
|
|
||||||
unit.set_property(ENABLE_IO, Scope::Input, Element::Input, Some(&enable))
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio enable input: {e}")))?;
|
|
||||||
|
|
||||||
// 48 kHz Int16 mono on both buses.
|
|
||||||
let fmt = StreamFormat {
|
|
||||||
sample_rate: SAMPLE_RATE_HZ,
|
|
||||||
sample_format: SampleFormat::I16,
|
|
||||||
flags: LinearPcmFlags::IS_SIGNED_INTEGER | LinearPcmFlags::IS_PACKED,
|
|
||||||
channels: 1,
|
|
||||||
};
|
|
||||||
unit.set_stream_format(fmt, Scope::Input, Element::Output)
|
|
||||||
.map_err(|e| AudioError::StreamConfig(format!("remoteio fmt output: {e}")))?;
|
|
||||||
unit.set_stream_format(fmt, Scope::Output, Element::Input)
|
|
||||||
.map_err(|e| AudioError::StreamConfig(format!("remoteio fmt input: {e}")))?;
|
|
||||||
|
|
||||||
// Shared render-reference buffer (INV_011 / INV_012).
|
|
||||||
let render_ref_buf = RenderReferenceBuffer::new();
|
|
||||||
let render_ref_for_capture = render_ref_buf.clone();
|
|
||||||
|
|
||||||
let mut capture_state = RawCaptureState::new(¶ms, render_ref_for_capture)?;
|
|
||||||
|
|
||||||
unit.set_input_callback(move |args: render_callback::Args<data::Interleaved<i16>>| {
|
|
||||||
capture_state.ingest_i16(args.data.buffer);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio input cb: {e}")))?;
|
|
||||||
|
|
||||||
let mut scratch = [0.0_f32; RAW_RENDER_SCRATCH_FRAMES * 2];
|
|
||||||
let mut mono = [0.0_f32; RAW_RENDER_SCRATCH_FRAMES];
|
|
||||||
let mut render_ref_accum = RenderReferenceFrameAccumulator::new();
|
|
||||||
let handler = params.handler.clone();
|
|
||||||
let output_gain = params.output_gain.clone();
|
|
||||||
let output_muted = params.output_muted.clone();
|
|
||||||
let stats_render = params.audio_processing_stats.clone();
|
|
||||||
|
|
||||||
unit.set_render_callback(move |args: render_callback::Args<data::Interleaved<i16>>| {
|
|
||||||
let out = args.data.buffer;
|
|
||||||
let n = out.len();
|
|
||||||
let process_n = n.min(RAW_RENDER_SCRATCH_FRAMES);
|
|
||||||
let stereo_n = process_n * 2;
|
|
||||||
if n > RAW_RENDER_SCRATCH_FRAMES {
|
|
||||||
stats_render.increment_callback_xrun();
|
|
||||||
}
|
|
||||||
scratch[..stereo_n].fill(0.0);
|
|
||||||
|
|
||||||
match handler.try_lock() {
|
|
||||||
Ok(mut h) => {
|
|
||||||
let _ = h.fill_buffer(&mut scratch[..stereo_n]);
|
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::WouldBlock) => {
|
|
||||||
stats_render.increment_callback_xrun();
|
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::Poisoned(e)) => {
|
|
||||||
warn!(target: "chanora_audio",
|
|
||||||
"AudioHandler poisoned (raw render): {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// INV_012: copy render reference BEFORE playout.
|
|
||||||
crate::voice_render::downmix_stereo_f32_to_mono_f32(
|
|
||||||
&scratch[..stereo_n],
|
|
||||||
&mut mono[..process_n],
|
|
||||||
);
|
|
||||||
render_ref_accum.push_mono_samples(&mono[..process_n], |frame| {
|
|
||||||
render_ref_buf.write(frame);
|
|
||||||
});
|
|
||||||
|
|
||||||
let gain = f32::from_bits(output_gain.load(Ordering::Relaxed));
|
|
||||||
let muted = output_muted.load(Ordering::Relaxed);
|
|
||||||
let mix_stats = crate::voice_render::downmix_stereo_f32_to_interleaved_i16(
|
|
||||||
&scratch[..stereo_n],
|
|
||||||
&mut out[..process_n],
|
|
||||||
1,
|
|
||||||
gain,
|
|
||||||
muted,
|
|
||||||
);
|
|
||||||
if process_n < n {
|
|
||||||
out[process_n..].fill(0);
|
|
||||||
}
|
|
||||||
if mix_stats.clipped_samples > 0 {
|
|
||||||
stats_render.add_clipped_samples(mix_stats.clipped_samples);
|
|
||||||
}
|
|
||||||
stats_render.update_render(crate::frame::dbfs(&scratch[..stereo_n]), n as u32);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio render cb: {e}")))?;
|
|
||||||
|
|
||||||
unit.initialize()
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio init: {e}")))?;
|
|
||||||
unit.start()
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio start: {e}")))?;
|
|
||||||
|
|
||||||
info!(
|
|
||||||
target: "chanora_audio",
|
|
||||||
sample_rate_hz = SAMPLE_RATE_HZ,
|
|
||||||
"ios RemoteIO (Sonora experimental) started"
|
|
||||||
);
|
|
||||||
Ok(Self { unit })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Restart the unit after a route change (stop → uninit → init → start).
|
|
||||||
pub fn restart(&mut self) -> Result<(), AudioError> {
|
|
||||||
self.unit
|
|
||||||
.stop()
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio restart stop: {e}")))?;
|
|
||||||
self.unit
|
|
||||||
.uninitialize()
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio restart uninit: {e}")))?;
|
|
||||||
self.unit
|
|
||||||
.initialize()
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio restart init: {e}")))?;
|
|
||||||
self.unit
|
|
||||||
.start()
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio restart start: {e}")))?;
|
|
||||||
info!(target: "chanora_audio", "ios RemoteIO restarted");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pause the unit during an AVAudioSession interruption.
|
|
||||||
pub fn pause(&mut self) -> Result<(), AudioError> {
|
|
||||||
self.unit
|
|
||||||
.stop()
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio pause: {e}")))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resume the unit after an interruption ends.
|
|
||||||
pub fn resume(&mut self) -> Result<(), AudioError> {
|
|
||||||
self.unit
|
|
||||||
.start()
|
|
||||||
.map_err(|e| AudioError::Backend(format!("remoteio resume: {e}")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for IosRawUnit {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Err(e) = self.unit.stop() {
|
|
||||||
warn!(target: "chanora_audio", error = %e,
|
|
||||||
"ios RemoteIO stop on drop failed");
|
|
||||||
} else {
|
|
||||||
info!(target: "chanora_audio", "ios RemoteIO stopped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -406,16 +406,7 @@ impl IosCaptureState {
|
|||||||
speech: true,
|
speech: true,
|
||||||
}
|
}
|
||||||
} else if vad_backend == crate::VadBackend::SileroOnnx {
|
} else if vad_backend == crate::VadBackend::SileroOnnx {
|
||||||
match crate::vad::callback_vad_worker_policy(
|
if let Some(worker) = self.silero_coreml_worker.as_ref() {
|
||||||
voice_activity_mode,
|
|
||||||
vad_backend,
|
|
||||||
self.silero_coreml_worker.is_some(),
|
|
||||||
) {
|
|
||||||
crate::vad::VadWorkerPolicy::UseWorker => {
|
|
||||||
let worker = self
|
|
||||||
.silero_coreml_worker
|
|
||||||
.as_ref()
|
|
||||||
.expect("policy checked worker");
|
|
||||||
let enqueued = worker.try_send(capture_seq, &frame);
|
let enqueued = worker.try_send(capture_seq, &frame);
|
||||||
if !worker.is_stale(capture_seq) {
|
if !worker.is_stale(capture_seq) {
|
||||||
let p = worker.latest_probability();
|
let p = worker.latest_probability();
|
||||||
@@ -436,8 +427,7 @@ impl IosCaptureState {
|
|||||||
&frame,
|
&frame,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
crate::vad::VadWorkerPolicy::UseFallback => {
|
|
||||||
used_fallback_vad = true;
|
used_fallback_vad = true;
|
||||||
self.mark_vad_fallback_active(crate::VadBackend::SileroOnnx);
|
self.mark_vad_fallback_active(crate::VadBackend::SileroOnnx);
|
||||||
crate::vad::VoiceActivityDetector::process_10ms(
|
crate::vad::VoiceActivityDetector::process_10ms(
|
||||||
@@ -445,11 +435,6 @@ impl IosCaptureState {
|
|||||||
&frame,
|
&frame,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
crate::vad::VadWorkerPolicy::NotModelBacked => crate::vad::VadOutput {
|
|
||||||
probability: 1.0,
|
|
||||||
speech: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
crate::vad::VoiceActivityDetector::process_10ms(&mut self.vad_detector, &frame)
|
crate::vad::VoiceActivityDetector::process_10ms(&mut self.vad_detector, &frame)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,10 +65,7 @@ pub(crate) mod voice_render;
|
|||||||
mod sdl_output;
|
mod sdl_output;
|
||||||
|
|
||||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||||
mod ios_voice_unit;
|
mod ios_voice_unit;
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
pub mod ios_raw_unit;
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub mod android_voice_unit;
|
pub mod android_voice_unit;
|
||||||
|
|||||||
@@ -1062,10 +1062,8 @@ pub enum BridgeAudioRoute {
|
|||||||
/// Bridge iOS voice-processing mode.
|
/// Bridge iOS voice-processing mode.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum BridgeIosVoiceProcessingMode {
|
pub enum BridgeIosVoiceProcessingMode {
|
||||||
/// Shipping VPIO path.
|
/// Apple VoiceProcessingIO path.
|
||||||
PlatformVoiceProcessing,
|
PlatformVoiceProcessing,
|
||||||
/// Experimental Sonora path.
|
|
||||||
SonoraExperimental,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bridge processing backend.
|
/// Bridge processing backend.
|
||||||
@@ -1223,7 +1221,6 @@ impl From<BridgeIosVoiceProcessingMode> for chanora_core::IosVoiceProcessingMode
|
|||||||
fn from(mode: BridgeIosVoiceProcessingMode) -> Self {
|
fn from(mode: BridgeIosVoiceProcessingMode) -> Self {
|
||||||
match mode {
|
match mode {
|
||||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => Self::PlatformVoiceProcessing,
|
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => Self::PlatformVoiceProcessing,
|
||||||
BridgeIosVoiceProcessingMode::SonoraExperimental => Self::SonoraExperimental,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1234,7 +1231,6 @@ impl From<chanora_core::IosVoiceProcessingMode> for BridgeIosVoiceProcessingMode
|
|||||||
chanora_core::IosVoiceProcessingMode::PlatformVoiceProcessing => {
|
chanora_core::IosVoiceProcessingMode::PlatformVoiceProcessing => {
|
||||||
Self::PlatformVoiceProcessing
|
Self::PlatformVoiceProcessing
|
||||||
}
|
}
|
||||||
chanora_core::IosVoiceProcessingMode::SonoraExperimental => Self::SonoraExperimental,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2361,25 +2357,11 @@ pub async fn set_ios_voice_processing_mode(
|
|||||||
let config = BridgeAudioProcessingConfig {
|
let config = BridgeAudioProcessingConfig {
|
||||||
route: BridgeAudioRoute::Speaker,
|
route: BridgeAudioRoute::Speaker,
|
||||||
ios_mode: mode,
|
ios_mode: mode,
|
||||||
processing_backend: match mode {
|
processing_backend: BridgeAudioBackend::PlatformVoiceProcessing,
|
||||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => {
|
|
||||||
BridgeAudioBackend::PlatformVoiceProcessing
|
|
||||||
}
|
|
||||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeAudioBackend::WebrtcApm,
|
|
||||||
},
|
|
||||||
vad_backend: BridgeVadBackend::SileroOnnx,
|
vad_backend: BridgeVadBackend::SileroOnnx,
|
||||||
aec: match mode {
|
aec: BridgeEffectOwner::Platform,
|
||||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => BridgeEffectOwner::Platform,
|
ns: BridgeEffectOwner::Platform,
|
||||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::WebrtcApm,
|
agc: BridgeEffectOwner::Platform,
|
||||||
},
|
|
||||||
ns: match mode {
|
|
||||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => BridgeEffectOwner::Platform,
|
|
||||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::WebrtcApm,
|
|
||||||
},
|
|
||||||
agc: match mode {
|
|
||||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => BridgeEffectOwner::Platform,
|
|
||||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::WebrtcApm,
|
|
||||||
},
|
|
||||||
hpf_enabled: true,
|
hpf_enabled: true,
|
||||||
limiter_enabled: true,
|
limiter_enabled: true,
|
||||||
vad_hangover_ms: 500,
|
vad_hangover_ms: 500,
|
||||||
|
|||||||
@@ -2409,7 +2409,6 @@ impl SseDecode for crate::api::BridgeIosVoiceProcessingMode {
|
|||||||
let mut inner = <i32>::sse_decode(deserializer);
|
let mut inner = <i32>::sse_decode(deserializer);
|
||||||
return match inner {
|
return match inner {
|
||||||
0 => crate::api::BridgeIosVoiceProcessingMode::PlatformVoiceProcessing,
|
0 => crate::api::BridgeIosVoiceProcessingMode::PlatformVoiceProcessing,
|
||||||
1 => crate::api::BridgeIosVoiceProcessingMode::SonoraExperimental,
|
|
||||||
_ => unreachable!(
|
_ => unreachable!(
|
||||||
"Invalid variant for BridgeIosVoiceProcessingMode: {}",
|
"Invalid variant for BridgeIosVoiceProcessingMode: {}",
|
||||||
inner
|
inner
|
||||||
@@ -3445,7 +3444,6 @@ impl flutter_rust_bridge::IntoDart for crate::api::BridgeIosVoiceProcessingMode
|
|||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
match self {
|
match self {
|
||||||
Self::PlatformVoiceProcessing => 0.into_dart(),
|
Self::PlatformVoiceProcessing => 0.into_dart(),
|
||||||
Self::SonoraExperimental => 1.into_dart(),
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4215,7 +4213,6 @@ impl SseEncode for crate::api::BridgeIosVoiceProcessingMode {
|
|||||||
<i32>::sse_encode(
|
<i32>::sse_encode(
|
||||||
match self {
|
match self {
|
||||||
crate::api::BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => 0,
|
crate::api::BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => 0,
|
||||||
crate::api::BridgeIosVoiceProcessingMode::SonoraExperimental => 1,
|
|
||||||
_ => {
|
_ => {
|
||||||
unimplemented!("");
|
unimplemented!("");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user