Replace the no-op input callback from commit 1 with a real
capture pipeline that mirrors the cpal-side CaptureState in
engine.rs but is type-specialised for the i16 mono samples VPIO
delivers natively.
New IosCaptureState struct (private to ios_voice_unit.rs) owns:
* OpusEncoder configured for VoIP at 48 kHz mono (32 kbps,
complexity 10, inband FEC, packet-loss-perc 5 — identical
tuning to try_open_capture in engine.rs).
* pcm_accum: Vec<i16> with capacity 2*FRAME_SAMPLES_MONO, growing
if a VPIO callback ever delivers more than ~40 ms.
* opus_out: [u8; MAX_OPUS_FRAME] scratch.
* Cloned Arc<AtomicBool> transmit gate + Arc<AtomicU32> frames-sent
counter shared with AudioEngine.
ingest_i16 flow:
1. If PTT gate is off -> clear accumulator + return (matches cpal
behaviour, no pop on PTT-release edge).
2. Apply mic_gain. Fast-path when gain==1.0 skips the multiply +
saturate loop entirely; otherwise saturating mul-then-cast
keeps the signal in the i16 envelope.
3. Drain complete 20 ms / 960-sample frames from the accumulator,
encode via encoder.encode (i16 path, no float conversion
needed since VPIO already gave us i16), build OutPacket with
AudioData::C2S { codec: OpusVoice }, try_send on voice_out_tx.
4. Frame buffer is stack-allocated [i16; FRAME_SAMPLES_MONO] —
no per-callback heap allocation on the realtime audio thread.
VPIO setup changes in IosVoiceUnit::start:
* NEW: explicit kAudioOutputUnitProperty_EnableIO (=2003) with
value 1 on (Scope::Input, Element::Input) BEFORE the stream
format setters. VPIO's input element is OFF by default; without
this toggle no audio flows in and the input callback never
fires. Commit 1's comment claiming set_input_callback handles
this was wrong; coreaudio-rs's set_input_callback only installs
the kAudioOutputUnitProperty_SetInputCallback property, not
the EnableIO toggle.
* Apple's documented sequence (now matched):
1. AudioComponentInstanceNew -> AudioUnit::new_uninitialized
2. EnableIO on element 1 -> set_property(2003, ...)
3. Stream format both elems -> set_stream_format x2
4. Install callbacks -> set_input_callback + set_render_callback
5. AudioUnitInitialize -> unit.initialize
6. AudioOutputUnitStart -> unit.start
* set_input_callback closure now moves the IosCaptureState in
by value and calls ingest_i16 with args.data.buffer (the
&mut [i16] coreaudio-rs delivers after running AudioUnitRender
internally to pull the mic samples into a pre-allocated
AudioBufferList).
What this commit does NOT do:
* Output render callback is still a silence-emitting stub.
Commit 4 lands the AudioHandler::fill_buffer + i16 downmix.
* Route-change handling — commit 5.
Build verify on Mac (target aarch64-apple-ios): cargo check
clean in 1.18s, no errors, no warnings.
Build counter 62 -> 63 — About dialog shows v1.0.0-rc.8+63.
chanora_flutter
Chanora — cross-platform voice client for TeamSpeak-compatible servers.
Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
For help getting started with Flutter development, view the online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.