Delete ios_raw_unit.rs (538 lines) and all SonoraExperimental references from the core audio crate. The experimental RemoteIO path that bypassed Apple VPIO in favor of software WebRTC APM was never shipped and is no longer needed. VPIO is the sole production iOS audio backend. - Delete ios_raw_unit.rs entirely - Remove Raw variant from IosVoiceBackend enum in engine.rs - Remove SonoraExperimental from IosVoiceProcessingMode enum - Simplify validate_for_ios() (single-variant enum, no mode check) - Remove 2 SonoraExperimental validation tests - Remove ios_raw_unit module declaration from lib.rs - Remove include_str!-based debug_wav test for the deleted file
168 lines
5.8 KiB
Rust
168 lines
5.8 KiB
Rust
//! # `chanora_audio`
|
|
//!
|
|
//! Audio subsystem promoted from `poc/audio-capture-playback-spike`
|
|
//! and wired against `chanora_protocol`'s voice channels.
|
|
//!
|
|
//! ## What's wired in this Beta
|
|
//!
|
|
//! * Default-input capture via platform audio backends (Oboe on Android,
|
|
//! VoiceProcessingIO on Apple platforms, cpal/SDL elsewhere)
|
|
//! * Frame-aligned 20 ms / 48 kHz mono Opus encoding via `audiopus`
|
|
//! * Forward encoded frames to the protocol crate as `OutPacket`s
|
|
//! * Inbound voice packets fed to `tsclientlib::audio::AudioHandler`
|
|
//! which owns Opus decode + per-client jitter buffer + mix
|
|
//! * Mixed f32 PCM pulled by the platform output callback at 48 kHz stereo
|
|
//! * Push-to-talk: capture stream is permanently open; encoding is
|
|
//! gated by an atomic `ptt_active` flag
|
|
//!
|
|
//! ## Voice processing in this Beta
|
|
//!
|
|
//! * iOS/macOS use Apple's VoiceProcessingIO path, which owns platform
|
|
//! AEC / AGC / noise suppression for the shipping route.
|
|
//! * Rust owns VoiceActivity transmit gating and exposes a software
|
|
//! processor surface for debug/future raw routes.
|
|
//! * Hot-plug device-change handling is still platform-specific follow-up work.
|
|
//! * Sample-rate adaptation if the device cannot do 48 kHz / mono in
|
|
//! the format we request (returns `AudioError::StreamConfig`)
|
|
//! * Multi-channel speaker layouts beyond stereo
|
|
|
|
#![warn(missing_docs)]
|
|
|
|
#[cfg(any(target_os = "android", test))]
|
|
#[cfg_attr(not(target_os = "android"), allow(dead_code))]
|
|
mod android_render_ring;
|
|
#[cfg(any(target_os = "android", test))]
|
|
#[cfg_attr(not(target_os = "android"), allow(dead_code))]
|
|
mod audio_event_queue;
|
|
pub mod audio_processing;
|
|
#[cfg_attr(not(target_os = "android"), allow(dead_code))]
|
|
mod capture_accumulator;
|
|
#[cfg_attr(not(target_os = "android"), allow(dead_code))]
|
|
mod capture_resampler;
|
|
pub mod debug_wav;
|
|
mod engine;
|
|
pub mod frame;
|
|
pub mod mobile_voice_backend;
|
|
pub mod mode_stack;
|
|
pub(crate) mod opus_voice;
|
|
pub mod processor;
|
|
pub mod ptt;
|
|
pub mod ptt_backends;
|
|
pub mod release_tail;
|
|
#[cfg_attr(
|
|
not(any(target_os = "android", target_os = "ios", test)),
|
|
allow(dead_code)
|
|
)]
|
|
pub(crate) mod render_reference;
|
|
pub mod route_policy;
|
|
pub mod transmit_mode;
|
|
pub mod transmit_selector;
|
|
pub mod vad;
|
|
pub mod voice_activity;
|
|
pub(crate) mod voice_render;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
mod sdl_output;
|
|
|
|
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
|
mod ios_voice_unit;
|
|
|
|
#[cfg(target_os = "android")]
|
|
pub mod android_voice_unit;
|
|
|
|
pub use audio_processing::{
|
|
AudioBackend, AudioProcessingConfig, AudioProcessingStats, AudioRoute, EffectOwner,
|
|
IosVoiceProcessingMode, SharedAudioProcessingStats, VadBackend,
|
|
};
|
|
pub use engine::list_audio_devices;
|
|
pub use engine::{AudioDeviceInfo, AudioDeviceList, AudioEngine, AudioEngineConfig};
|
|
|
|
// SDD-120 §3 bench seam — `#[doc(hidden)]` re-export so the criterion
|
|
// bench harness under `crates/chanora_audio/benches/` can construct a
|
|
// CaptureState and drive `ingest` without re-implementing the engine.
|
|
// Not part of the supported public API.
|
|
#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))]
|
|
#[doc(hidden)]
|
|
pub use engine::bench_seam;
|
|
pub use ptt::{AudioTransmitGate, MissedKeyUpWatchdog, PttBackendDescriptor, PttCapabilityLevel};
|
|
pub use ptt_backends::{
|
|
select as select_ptt_backend, DesktopPttBackend, FocusedPttBackend, PttBackendError,
|
|
PttBinding, PttInputClass,
|
|
};
|
|
pub use release_tail::{ReleaseTailTimer, DEFAULT_TAIL_MS, MAX_TAIL_MS};
|
|
pub use transmit_mode::TransmitMode;
|
|
pub use transmit_selector::{PermissionGate, TransmitModeSelector};
|
|
|
|
use thiserror::Error;
|
|
|
|
/// Errors raised by the audio subsystem.
|
|
#[derive(Debug, Error)]
|
|
pub enum AudioError {
|
|
/// Platform did not expose a usable default input device.
|
|
#[error("no default input device")]
|
|
NoInputDevice,
|
|
/// Platform did not expose a usable default output device.
|
|
#[error("no default output device")]
|
|
NoOutputDevice,
|
|
/// The audio backend rejected a stream configuration.
|
|
#[error("stream config rejected: {0}")]
|
|
StreamConfig(String),
|
|
/// Opus codec init/encode/decode failure.
|
|
#[error("opus: {0}")]
|
|
Opus(String),
|
|
/// A backend-specific failure surfaced without a typed mapping.
|
|
#[error("audio backend: {0}")]
|
|
Backend(String),
|
|
/// Android platform not ready — ndk_context not initialised before
|
|
/// audio engine start. This occurs when `initChanoraContext` has not
|
|
/// been called before `voice_join` triggers the audio engine.
|
|
#[error("android platform not ready: ndk_context not initialised")]
|
|
PlatformNotReady,
|
|
/// Audio processing config failed validation.
|
|
#[error("invalid audio processing config: {0}")]
|
|
InvalidAudioProcessingConfig(String),
|
|
/// Requested audio processing config is schema-visible but not implemented.
|
|
#[error("unsupported audio processing config: {0}")]
|
|
UnsupportedAudioProcessingConfig(String),
|
|
}
|
|
|
|
/// Audio-effect toggles. Defaults match DEC-007 (AEC),
|
|
/// DEC-008 (AGC), DEC-009 (NS), DEC-010 (HPF) — all enabled.
|
|
///
|
|
/// On iOS/macOS these map to VoiceProcessingIO-owned effects in the
|
|
/// default route. Software processor backends may also consult them
|
|
/// on raw/debug routes.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct AudioEffects {
|
|
/// Acoustic echo cancellation (DEC-007).
|
|
pub aec: bool,
|
|
/// Automatic gain control (DEC-008).
|
|
pub agc: bool,
|
|
/// Noise suppression (DEC-009).
|
|
pub noise_suppression: bool,
|
|
/// High-pass filter (DEC-010).
|
|
pub high_pass: bool,
|
|
}
|
|
|
|
impl Default for AudioEffects {
|
|
fn default() -> Self {
|
|
Self {
|
|
aec: true,
|
|
agc: true,
|
|
noise_suppression: true,
|
|
high_pass: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn defaults_match_decisions() {
|
|
let e = AudioEffects::default();
|
|
assert!(e.aec && e.agc && e.noise_suppression && e.high_pass);
|
|
}
|
|
}
|