fix(audio): eliminate Android output stutter via Oboe config + lock-free callback (#20)

* fix(audio): eliminate Android output stutter via Oboe config + lock-free callback

Phase 1 — Oboe configuration:
- Change output stream from Usage::VoiceCommunication to Usage::Game with
  ContentType::Sonification to avoid forcing the Legacy (OpenSL ES) data
  path on most devices (Oboe issue #2075)
- Switch output format from i16 Mono to f32 Stereo, matching Qint's proven
  configuration and eliminating per-callback downmix conversion
- Set buffer size to 2x burst after stream open, reducing default buffer
  from 8-20x burst to 2x burst for lower latency
- Remove scratch Mutex<Vec<f32>>; callback writes directly to Oboe buffer

Phase 2 — Lock-free output callback:
- Add audio_event_queue.rs: lock-free SPSC bridge using crossbeam ArrayQueue
  with separate packet (lossy) and control (reliable) channels
- OutputCallback now owns AudioHandler directly (no Arc<Mutex<>> on Android)
- Inbound forwarder pushes packets via AudioEventProducer (no mutex)
- set_client_volume pushes control commands via event queue on Android
- iOS/desktop Arc<Mutex<AudioHandler>> path unchanged

* fix(audio): address PR #20 review findings

- Store AudioEventConsumer directly in OutputCallback to eliminate
  per-callback Arc clone on the real-time audio thread
- Add SAFETY comment for the unsafe from_raw_parts_mut transmute
- Bound set_client_volume spin-loop to 64 retries with warn log
- Remove redundant crossbeam-utils direct dependency
- Regenerate license inventory for new crossbeam deps (CI fix)

* fix(audio): use ASCII TODO punctuation
This commit is contained in:
Edison Jwa
2026-06-05 13:57:53 +09:00
committed by GitHub
parent fb2a8e0a80
commit 2902a8bcd5
8 changed files with 510 additions and 82 deletions
@@ -67,7 +67,7 @@ pub type BackendEventTx = mpsc::UnboundedSender<BackendEvent>;
pub type AudioSessionId = i32;
/// Engine-owned state shared with mobile voice audio callbacks.
#[derive(Clone)]
#[cfg_attr(not(target_os = "android"), derive(Clone))]
pub(crate) struct VoiceAudioParams {
/// Opus-encoded voice packets sent on this channel toward the
/// protocol layer.
@@ -78,8 +78,15 @@ pub(crate) struct VoiceAudioParams {
pub frames_sent: Arc<AtomicU32>,
/// Pre-encode amplitude scale (1.0 = unity).
pub mic_gain: f32,
/// AudioHandler owned by the Android output callback.
#[cfg(target_os = "android")]
pub handler: AudioHandler<SessionAudioId>,
/// Producer used by Android engine tasks to feed the output callback.
#[cfg(target_os = "android")]
pub event_producer: crate::audio_event_queue::AudioEventProducer,
/// AudioHandler that inbound decode+mix feeds into; the output
/// callback pulls mixed stereo f32 from it.
#[cfg(not(target_os = "android"))]
pub handler: Arc<Mutex<AudioHandler<SessionAudioId>>>,
/// Master output gain (f32 bits stored in AtomicU32 for lock-free
/// cross-thread read from the realtime audio callback).