From 5de6eccc0c3a0a3846ee19652b9398b85c6b3e0e Mon Sep 17 00:00:00 2001 From: EdisonJwa Date: Sun, 17 May 2026 00:53:39 +0800 Subject: [PATCH] fix(audio,ios): correct ios_voice_unit imports + iOS stop() field access (rc.8+61) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iOS build of chanora_audio (target aarch64-apple-ios) failed with four compilation errors after commit 2 landed. Root causes were all simple symbol-path / cfg-gating mistakes from the skeleton commit; the underlying design is unchanged. 1. ios_voice_unit.rs: wrong import path for OutPacket. The chanora_protocol crate re-exports it at the crate root (`pub use ...::OutPacket` in lib.rs line 52), not from a `voice` submodule (which doesn't exist). - use chanora_protocol::voice::OutPacket; + use chanora_protocol::OutPacket; 2. ios_voice_unit.rs: LinearPcmFlags lives in `coreaudio::audio_unit::audio_format`, not in `stream_format` (the doc page lists it under StreamFormat but the actual module path is the upstream Apple naming). - use coreaudio::audio_unit::stream_format::LinearPcmFlags; + use coreaudio::audio_unit::audio_format::LinearPcmFlags; 3. ios_voice_unit.rs: `Ordering` import unused (commit 1 skeleton callbacks don't load atomics yet — that comes in commits 3 + 4). Remove from the std::sync::atomic import to silence the unused_imports warning. 4. engine.rs::AudioEngine::stop(): the existing body unconditionally touched self._input_stream and self._output_stream, but commit 2 cfg-gated those fields away on iOS (and added an iOS-only _ios_voice_unit field in their place). Split the field drop logic with the same target_os = "ios" cfg so each platform only touches the fields it actually has. Also clean up two pre-existing warnings exposed by the iOS cfg gating: 5. engine.rs: `tracing::{error, warn}` were imported unconditionally but are only used inside cpal log lines. Cfg-gate the import to not(target_os = "ios"). 6. engine.rs: `AudioData`, `CodecType`, `OutAudio` from chanora_protocol are only referenced in the Opus encoder feed inside CaptureState — cpal-side only. Cfg-gate to not(target_os = "ios"); keep `InboundVoice` + `OutPacket` on the unconditional path because the inbound forwarder + (in commit 3) the iOS capture pipeline both reference them. Also fix a stray duplicate `#[cfg(not(target_os = "ios"))]` attribute that landed on line 18 in commit 2. Build verify (Linux host): cargo check -p chanora_audio clean in 0.53s. iOS-side check pending on Mac. Build counter bumped 60 -> 61 — the About dialog will display v1.0.0-rc.8+61 so the user can confirm the build under test matches this commit. --- apps/chanora_flutter/pubspec.yaml | 2 +- crates/chanora_audio/src/engine.rs | 40 +++++++++++++++++----- crates/chanora_audio/src/ios_voice_unit.rs | 10 +++--- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/apps/chanora_flutter/pubspec.yaml b/apps/chanora_flutter/pubspec.yaml index 2c30fbc..9b6ccac 100644 --- a/apps/chanora_flutter/pubspec.yaml +++ b/apps/chanora_flutter/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0-rc.8+60 +version: 1.0.0-rc.8+61 environment: sdk: ^3.11.5 diff --git a/crates/chanora_audio/src/engine.rs b/crates/chanora_audio/src/engine.rs index ed6e7ca..dbb762d 100644 --- a/crates/chanora_audio/src/engine.rs +++ b/crates/chanora_audio/src/engine.rs @@ -13,9 +13,14 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; #[cfg(not(target_os = "ios"))] use cpal::{SampleFormat, SizedSample}; use tokio::sync::mpsc; -use tracing::{debug, error, info, warn}; - +use tracing::{debug, info}; +// `error!` and `warn!` are used only inside the cpal capture / +// playback paths (`build_input_stream`, `build_output_stream`, +// `try_open_capture` log lines). Cfg-gate the imports too so iOS +// builds don't carry an unused-imports warning. #[cfg(not(target_os = "ios"))] +use tracing::{error, warn}; + #[cfg(not(target_os = "ios"))] use audiopus::coder::Encoder as OpusEncoder; #[cfg(not(target_os = "ios"))] @@ -26,9 +31,16 @@ use audiopus::{ use tsclientlib::audio::AudioHandler; -use chanora_protocol::{ - AudioData, CodecType, InboundVoice, OutAudio, OutPacket, -}; +// `AudioData`, `CodecType`, `OutAudio` are referenced only by the +// cpal capture pipeline's Opus encode path (`CaptureState::encode_and_send`). +// `InboundVoice` + `OutPacket` are used by every platform — the +// inbound forwarder task pumps `InboundVoice` into AudioHandler on +// iOS too, and `OutPacket` flows out of the capture pipeline once +// commit 3 lands. Cfg-gate the cpal-only ones to keep iOS warnings +// clean. +#[cfg(not(target_os = "ios"))] +use chanora_protocol::{AudioData, CodecType, OutAudio}; +use chanora_protocol::{InboundVoice, OutPacket}; use crate::AudioError; @@ -641,12 +653,24 @@ impl AudioEngine { // The platform PTT backend is no longer owned by the // engine (SDD-088); its lifecycle is managed by // `chanora_core::ptt::PttController`. The engine only - // needs to abort its watchdog and drop the cpal streams. + // needs to abort its watchdog and drop the audio streams. // Aborting the watchdog cancels its tokio task. self.ptt_watchdog.take(); // Drop the streams, which stops their callback threads. - let _ = self._input_stream.lock().unwrap().take(); - let _ = self._output_stream.lock().unwrap().take(); + // Each platform has a slightly different backend; the + // common contract is that dropping the wrapper stops + // audio. iOS collapses input + output into one + // `IosVoiceUnit` (see `ios_voice_unit.rs`); every other + // platform has separate cpal input + cpal/SDL output. + #[cfg(not(target_os = "ios"))] + { + let _ = self._input_stream.lock().unwrap().take(); + let _ = self._output_stream.lock().unwrap().take(); + } + #[cfg(target_os = "ios")] + { + let _ = self._ios_voice_unit.lock().unwrap().take(); + } info!(target: "chanora_audio", "audio engine stopped"); } diff --git a/crates/chanora_audio/src/ios_voice_unit.rs b/crates/chanora_audio/src/ios_voice_unit.rs index 0a7a867..d5bbf3d 100644 --- a/crates/chanora_audio/src/ios_voice_unit.rs +++ b/crates/chanora_audio/src/ios_voice_unit.rs @@ -75,14 +75,12 @@ //! * AVAudioSession category / mode configuration — Swift owns the //! session (it must be set up before Flutter loads). -use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU32}; use std::sync::{Arc, Mutex}; +use coreaudio::audio_unit::audio_format::LinearPcmFlags; use coreaudio::audio_unit::render_callback::{self, data}; -use coreaudio::audio_unit::{ - AudioUnit, Element, SampleFormat, Scope, StreamFormat, -}; -use coreaudio::audio_unit::stream_format::LinearPcmFlags; +use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; use coreaudio::audio_unit::IOType; use tokio::sync::mpsc; use tracing::{info, warn}; @@ -90,7 +88,7 @@ use tsclientlib::audio::AudioHandler; use crate::engine::SessionAudioId; use crate::AudioError; -use chanora_protocol::voice::OutPacket; +use chanora_protocol::OutPacket; /// 20 ms at 48 kHz mono — one Opus frame's worth of samples. /// Aligning the AudioUnit IO buffer to this frame size keeps the