//! # `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 `cpal` (DEC-011.1) //! * 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 cpal output callback at 48 kHz stereo //! * Push-to-talk: capture stream is permanently open; encoding is //! gated by an atomic `ptt_active` flag //! //! ## What's NOT wired in this Beta //! //! * AEC / AGC / NS / HPF DSP chain (DEC-007/008/009/010 — Beta+ //! work; the toggles in `AudioEffects` are honoured by *naming* //! but the filters are no-ops) //! * Mobile audio paths (DEC-011.1 desktop + Android proven; this //! integration is desktop-only for v0.2.0-beta.1) //! * Hot-plug device-change handling //! * 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)] mod engine; pub mod ptt; pub mod ptt_backends; pub mod release_tail; pub mod transmit_mode; pub mod transmit_selector; #[cfg(target_os = "linux")] mod sdl_output; #[cfg(target_os = "ios")] mod ios_voice_unit; pub use engine::{AudioEngine, AudioEngineConfig}; 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::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), } /// Audio-effect toggles. Defaults match DEC-007 (AEC), /// DEC-008 (AGC), DEC-009 (NS), DEC-010 (HPF) — all enabled. /// /// Note: in Beta v0.2.0-beta.1 the actual DSP filters are not yet /// implemented; the struct is kept here as the public API surface so /// later work can flip an internal flag without breaking callers. #[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); } }