Files
chanora/apps/chanora_flutter
EdisonJwa 1aa514df75 feat(audio,ios): wire VPIO input callback to Opus encoder (commit 3/5, rc.8+63)
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.
2026-05-17 01:11:11 +08:00
..

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.