@@ -94,7 +94,11 @@ enum AudioEngineErrorCode {
9494
9595 // Voice processing errors
9696 kAudioEngineVoiceProcessingError = -8000 ,
97- kAudioEngineAGCError = -8001
97+ kAudioEngineAGCError = -8001 ,
98+
99+ // Permission and session errors
100+ kAudioEngineErrorInsufficientDevicePermission = -9000 ,
101+ kAudioEngineErrorAudioSessionInvalidCategory = -9001
98102};
99103
100104class FineAudioBuffer;
@@ -124,8 +128,6 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver
124128 bool output_available = true ;
125129 bool input_available = true ;
126130
127- // Output will be enabled when input is enabled
128- bool input_follow_mode = true ;
129131 bool input_enabled_persistent_mode = false ;
130132
131133 bool input_muted = true ;
@@ -150,7 +152,6 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver
150152 return input_enabled == rhs.input_enabled && input_running == rhs.input_running &&
151153 output_enabled == rhs.output_enabled && output_running == rhs.output_running &&
152154 input_available == rhs.input_available && output_available == rhs.output_available &&
153- input_follow_mode == rhs.input_follow_mode &&
154155 input_enabled_persistent_mode == rhs.input_enabled_persistent_mode &&
155156 input_muted == rhs.input_muted && is_interrupted == rhs.is_interrupted &&
156157 render_mode == rhs.render_mode && mute_mode == rhs.mute_mode &&
@@ -165,27 +166,64 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver
165166
166167 bool operator!=(const EngineState& rhs) const { return !(*this == rhs); }
167168
168- bool IsOutputInputLinked () const { return input_follow_mode && voice_processing_enabled; }
169+ // AUDIO STATE LOGIC
170+ //
171+ // Device Mode:
172+ // - Output follows input only when voice_processing_enabled=true (for AEC)
173+ // - Input respects mute mode restrictions (RestartEngine + input_muted)
174+ // - Independent operation when voice processing is disabled
175+ //
176+ // Manual Mode:
177+ // - Bidirectional coupling: if ANY component is enabled/running, BOTH are considered
178+ // enabled/running
179+ // - No mute mode restrictions (manual control bypasses automatic muting)
180+ // - Required for AVAudioEngine graph connectivity (input must connect to output)
181+ //
182+ // All modes respect availability flags (input_available/output_available)
169183
170184 bool IsOutputEnabled () const {
171- bool result = IsOutputInputLinked () ? (IsInputEnabled () || output_enabled) : output_enabled;
172- return output_available && result;
185+ if (!output_available) return false ;
186+
187+ switch (render_mode) {
188+ case RenderMode::Device:
189+ return voice_processing_enabled ? (IsInputEnabled () || output_enabled) : output_enabled;
190+ case RenderMode::Manual:
191+ return output_enabled || input_enabled || input_enabled_persistent_mode;
192+ }
173193 }
174194
175195 bool IsOutputRunning () const {
176- bool result = IsOutputInputLinked () ? (IsInputRunning () || output_running) : output_running;
177- return output_available && result;
196+ if (!output_available) return false ;
197+
198+ switch (render_mode) {
199+ case RenderMode::Device:
200+ return voice_processing_enabled ? (IsInputRunning () || output_running) : output_running;
201+ case RenderMode::Manual:
202+ return output_running || input_running;
203+ }
178204 }
179205
180206 bool IsInputEnabled () const {
181- bool result = !(mute_mode == MuteMode::RestartEngine && input_muted) &&
182- (input_enabled || input_enabled_persistent_mode);
183- return input_available && result;
207+ if (!input_available) return false ;
208+
209+ switch (render_mode) {
210+ case RenderMode::Device:
211+ return !(mute_mode == MuteMode::RestartEngine && input_muted) &&
212+ (input_enabled || input_enabled_persistent_mode);
213+ case RenderMode::Manual:
214+ return input_enabled || input_enabled_persistent_mode || output_enabled;
215+ }
184216 }
185217
186218 bool IsInputRunning () const {
187- bool result = !(mute_mode == MuteMode::RestartEngine && input_muted) && input_running;
188- return input_available && result;
219+ if (!input_available) return false ;
220+
221+ switch (render_mode) {
222+ case RenderMode::Device:
223+ return !(mute_mode == MuteMode::RestartEngine && input_muted) && input_running;
224+ case RenderMode::Manual:
225+ return input_running || output_running;
226+ }
189227 }
190228
191229 bool IsAnyEnabled () const { return IsInputEnabled () || IsOutputEnabled (); }
@@ -413,7 +451,6 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver
413451
414452 EngineState engine_state_ RTC_GUARDED_BY (thread_);
415453
416- bool IsMicrophonePermissionGranted ();
417454 int32_t ModifyEngineState (std::function<EngineState (EngineState)> state_transform);
418455
419456 int32_t ApplyDeviceEngineState (EngineStateUpdate state);
@@ -441,6 +478,13 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver
441478 std::vector<std::string> input_device_labels_;
442479#endif
443480
481+ bool IsMicrophonePermissionGranted ();
482+ bool EnsureMicrophonePermissionSync ();
483+
484+ #if !TARGET_OS_OSX
485+ bool IsAudioSessionCategoryValid (NSString * category, bool is_input_enabled,
486+ bool is_output_enabled);
487+ #endif
444488 void DebugAudioEngine ();
445489
446490 void StartRenderLoop ();
0 commit comments