From 73bfc6a0cdcf935c0fa58270b9b3beb2d964e3fa Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Fri, 19 Sep 2025 18:09:41 +0800 Subject: [PATCH 1/2] Audio engine input / output availability (#196) Adds a flag to override engine start/stop behavior, useful in scenarios such as CallKit, where the timing for starting AVAudioSession and AVAudioEngine is restricted. --- modules/audio_device/audio_engine_device.h | 30 +++++++++++-------- modules/audio_device/audio_engine_device.mm | 29 ++++++++++++++++-- .../api/peerconnection/RTCAudioDeviceModule.h | 29 +++++++++++------- .../peerconnection/RTCAudioDeviceModule.mm | 24 +++++++++++++++ 4 files changed, 87 insertions(+), 25 deletions(-) diff --git a/modules/audio_device/audio_engine_device.h b/modules/audio_device/audio_engine_device.h index b9a8c26e54..c484f649d0 100644 --- a/modules/audio_device/audio_engine_device.h +++ b/modules/audio_device/audio_engine_device.h @@ -122,6 +122,9 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver bool output_enabled = false; bool output_running = false; + bool output_available = true; + bool input_available = true; + // Output will be enabled when input is enabled bool input_follow_mode = true; bool input_enabled_persistent_mode = false; @@ -151,6 +154,7 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver bool operator==(const EngineState& rhs) const { return input_enabled == rhs.input_enabled && input_running == rhs.input_running && output_enabled == rhs.output_enabled && output_running == rhs.output_running && + input_available == rhs.input_available && output_available == rhs.output_available && input_follow_mode == rhs.input_follow_mode && input_enabled_persistent_mode == rhs.input_enabled_persistent_mode && input_muted == rhs.input_muted && is_interrupted == rhs.is_interrupted && @@ -172,33 +176,29 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver bool IsOutputInputLinked() const { return input_follow_mode && voice_processing_enabled; } bool IsOutputEnabled() const { - return IsOutputInputLinked() ? (IsInputEnabled() || output_enabled) : output_enabled; + bool result = IsOutputInputLinked() ? (IsInputEnabled() || output_enabled) : output_enabled; + return output_available && result; } bool IsOutputRunning() const { - return IsOutputInputLinked() ? (IsInputRunning() || output_running) : output_running; + bool result = IsOutputInputLinked() ? (IsInputRunning() || output_running) : output_running; + return output_available && result; } bool IsInputEnabled() const { - return !(mute_mode == MuteMode::RestartEngine && input_muted) && - (input_enabled || input_enabled_persistent_mode); + bool result = !(mute_mode == MuteMode::RestartEngine && input_muted) && + (input_enabled || input_enabled_persistent_mode); + return input_available && result; } bool IsInputRunning() const { - return !(mute_mode == MuteMode::RestartEngine && input_muted) && input_running; + bool result = !(mute_mode == MuteMode::RestartEngine && input_muted) && input_running; + return input_available && result; } bool IsAnyEnabled() const { return IsInputEnabled() || IsOutputEnabled(); } bool IsAnyRunning() const { return IsInputRunning() || IsOutputRunning(); } - bool IsAllEnabled() const { - return IsOutputInputLinked() ? IsInputEnabled() : IsInputEnabled() && output_enabled; - } - - bool IsAllRunning() const { - return IsOutputInputLinked() ? input_running : input_running && output_running; - } - bool IsOutputDefaultDevice() const { #if TARGET_OS_OSX return output_device_id == kAudioObjectUnknown; @@ -318,6 +318,9 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver int32_t SetEngineState(EngineState enable); int32_t GetEngineState(EngineState* enabled); + int32_t SetEngineAvailability(bool input_available, bool output_available); + int32_t EngineAvailability(bool* input_available, bool* output_available); + int32_t SetObserver(AudioDeviceObserver* observer) override; bool IsAudioEngineDevice() const override { return true; } @@ -440,6 +443,7 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver bool IsMicrophonePermissionGranted(); void ResetEngineState(); int32_t ModifyEngineState(std::function state_transform); + int32_t ApplyDeviceEngineState(EngineStateUpdate& state); int32_t ApplyManualEngineState(EngineStateUpdate& state); diff --git a/modules/audio_device/audio_engine_device.mm b/modules/audio_device/audio_engine_device.mm index 18b417404f..63fd590330 100644 --- a/modules/audio_device/audio_engine_device.mm +++ b/modules/audio_device/audio_engine_device.mm @@ -1260,6 +1260,33 @@ return result; } +int32_t AudioEngineDevice::SetEngineAvailability(bool input_available, bool output_available) { + RTC_DCHECK_RUN_ON(thread_); + LOGI() << "SetEngineAvailability: " << input_available << " " << output_available; + + int32_t result = + ModifyEngineState([input_available, output_available](EngineState state) -> EngineState { + state.input_available = input_available; + state.output_available = output_available; + return state; + }); + + return result; +} + +int32_t AudioEngineDevice::EngineAvailability(bool* input_available, bool* output_available) { + RTC_DCHECK_RUN_ON(thread_); + + if (input_available == nullptr || output_available == nullptr) { + return -1; + } + + *input_available = engine_state_.input_available; + *output_available = engine_state_.output_available; + + return 0; +} + int32_t AudioEngineDevice::ManualRenderingMode(bool* enabled) { LOGI() << "ManualRenderingMode"; RTC_DCHECK_RUN_ON(thread_); @@ -2814,8 +2841,6 @@ << ", IsInputRunning: " << state.IsInputRunning() << ", IsAnyEnabled: " << state.IsAnyEnabled() << ", IsAnyRunning: " << state.IsAnyRunning() - << ", IsAllEnabled: " << state.IsAllEnabled() - << ", IsAllRunning: " << state.IsAllRunning() << ", AdvancedDucking: " << state.advanced_ducking << ", DuckingLevel: " << state.ducking_level << ", MuteMode: " << static_cast(state.mute_mode) diff --git a/sdk/objc/api/peerconnection/RTCAudioDeviceModule.h b/sdk/objc/api/peerconnection/RTCAudioDeviceModule.h index ae038f48ce..4ebf7b51cb 100644 --- a/sdk/objc/api/peerconnection/RTCAudioDeviceModule.h +++ b/sdk/objc/api/peerconnection/RTCAudioDeviceModule.h @@ -41,21 +41,26 @@ typedef NS_ENUM(NSInteger, RTC_OBJC_TYPE(RTCAudioEngineMuteMode)) { }; typedef struct { - bool outputEnabled; - bool outputRunning; - bool inputEnabled; - bool inputRunning; - bool inputMuted; + BOOL outputEnabled; + BOOL outputRunning; + BOOL inputEnabled; + BOOL inputRunning; + BOOL inputMuted; RTC_OBJC_TYPE(RTCAudioEngineMuteMode) muteMode; } RTC_OBJC_TYPE(RTCAudioEngineState); -typedef struct { - bool voiceProcessingEnabled; - bool voiceProcessingBypassed; - bool voiceProcessingAGCEnabled; - bool stereoPlayoutEnabled; +typedef struct { + bool voiceProcessingEnabled; + bool voiceProcessingBypassed; + bool voiceProcessingAGCEnabled; + bool stereoPlayoutEnabled; } RTC_OBJC_TYPE(RTCAudioProcessingState); +typedef struct { + BOOL isInputAvailable; + BOOL isOutputAvailable; +} RTC_OBJC_TYPE(RTCAudioEngineAvailability); + RTC_EXTERN NSString *const RTC_CONSTANT_TYPE(RTCAudioEngineInputMixerNodeKey); @class RTC_OBJC_TYPE(RTCAudioDeviceModule); @@ -153,6 +158,8 @@ RTC_OBJC_EXPORT - (NSInteger)initAndStartRecording; +- (NSInteger)setEngineAvailability:(RTC_OBJC_TYPE(RTCAudioEngineAvailability))availability; + // For testing purposes @property(nonatomic, readonly) BOOL isPlayoutInitialized; @property(nonatomic, readonly) BOOL isRecordingInitialized; @@ -205,6 +212,8 @@ RTC_OBJC_EXPORT @property(nonatomic, assign) BOOL prefersStereoPlayout; - (void)refreshStereoPlayoutState; +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCAudioEngineAvailability) engineAvailability; + @end NS_ASSUME_NONNULL_END diff --git a/sdk/objc/api/peerconnection/RTCAudioDeviceModule.mm b/sdk/objc/api/peerconnection/RTCAudioDeviceModule.mm index e8e2baf5b8..ab8565cb4e 100644 --- a/sdk/objc/api/peerconnection/RTCAudioDeviceModule.mm +++ b/sdk/objc/api/peerconnection/RTCAudioDeviceModule.mm @@ -566,6 +566,30 @@ - (void)setEngineState:(RTC_OBJC_TYPE(RTCAudioEngineState))state { #pragma mark - Unique to AudioEngineDevice +- (NSInteger)setEngineAvailability:(RTC_OBJC_TYPE(RTCAudioEngineAvailability))availability { + webrtc::AudioEngineDevice *module = static_cast(_native.get()); + if (module == nullptr) return -1; + + return _workerThread->BlockingCall([module, availability] { + return module->SetEngineAvailability(availability.isInputAvailable, + availability.isOutputAvailable); + }); +} + +- (RTC_OBJC_TYPE(RTCAudioEngineAvailability))engineAvailability { + webrtc::AudioEngineDevice *module = static_cast(_native.get()); + if (module == nullptr) return RTC_OBJC_TYPE(RTCAudioEngineAvailability)(NO, NO); + + return _workerThread->BlockingCall([module] { + bool input_available = false; + bool output_available = false; + int32_t result = module->EngineAvailability(&input_available, &output_available); + if (result != 0) return RTC_OBJC_TYPE(RTCAudioEngineAvailability)(NO, NO); + + return RTC_OBJC_TYPE(RTCAudioEngineAvailability)(input_available, output_available); + }); +} + - (BOOL)isRecordingAlwaysPreparedMode { #if !defined(WEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE) return NO; From 6d64997f6d739ad084046d73f5afd3ea9af12630 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Wed, 8 Jul 2026 18:11:08 +0530 Subject: [PATCH 2/2] review comments fix --- modules/audio_device/audio_engine_device.h | 5 +++++ modules/audio_device/audio_engine_device.mm | 4 ++++ sdk/objc/api/peerconnection/RTCAudioDeviceModule.h | 14 +++++++------- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/audio_device/audio_engine_device.h b/modules/audio_device/audio_engine_device.h index c484f649d0..f5ceab9c6c 100644 --- a/modules/audio_device/audio_engine_device.h +++ b/modules/audio_device/audio_engine_device.h @@ -122,6 +122,8 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver bool output_enabled = false; bool output_running = false; + // Default true: the engine may activate on init. + // Set false to gate start/stop (e.g. CallKit). bool output_available = true; bool input_available = true; @@ -199,6 +201,9 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver bool IsAnyEnabled() const { return IsInputEnabled() || IsOutputEnabled(); } bool IsAnyRunning() const { return IsInputRunning() || IsOutputRunning(); } + bool IsAllEnabled() const { return IsInputEnabled() && IsOutputEnabled(); } + bool IsAllRunning() const { return IsInputRunning() && IsOutputRunning(); } + bool IsOutputDefaultDevice() const { #if TARGET_OS_OSX return output_device_id == kAudioObjectUnknown; diff --git a/modules/audio_device/audio_engine_device.mm b/modules/audio_device/audio_engine_device.mm index 63fd590330..270e030d9d 100644 --- a/modules/audio_device/audio_engine_device.mm +++ b/modules/audio_device/audio_engine_device.mm @@ -2841,6 +2841,10 @@ << ", IsInputRunning: " << state.IsInputRunning() << ", IsAnyEnabled: " << state.IsAnyEnabled() << ", IsAnyRunning: " << state.IsAnyRunning() + << ", IsAllEnabled: " << state.IsAllEnabled() + << ", IsAllRunning: " << state.IsAllRunning() + << ", InputAvailable: " << state.input_available + << ", OutputAvailable: " << state.output_available << ", AdvancedDucking: " << state.advanced_ducking << ", DuckingLevel: " << state.ducking_level << ", MuteMode: " << static_cast(state.mute_mode) diff --git a/sdk/objc/api/peerconnection/RTCAudioDeviceModule.h b/sdk/objc/api/peerconnection/RTCAudioDeviceModule.h index 4ebf7b51cb..6123010caf 100644 --- a/sdk/objc/api/peerconnection/RTCAudioDeviceModule.h +++ b/sdk/objc/api/peerconnection/RTCAudioDeviceModule.h @@ -41,11 +41,11 @@ typedef NS_ENUM(NSInteger, RTC_OBJC_TYPE(RTCAudioEngineMuteMode)) { }; typedef struct { - BOOL outputEnabled; - BOOL outputRunning; - BOOL inputEnabled; - BOOL inputRunning; - BOOL inputMuted; + bool outputEnabled; + bool outputRunning; + bool inputEnabled; + bool inputRunning; + bool inputMuted; RTC_OBJC_TYPE(RTCAudioEngineMuteMode) muteMode; } RTC_OBJC_TYPE(RTCAudioEngineState); @@ -57,8 +57,8 @@ typedef struct { } RTC_OBJC_TYPE(RTCAudioProcessingState); typedef struct { - BOOL isInputAvailable; - BOOL isOutputAvailable; + bool isInputAvailable; + bool isOutputAvailable; } RTC_OBJC_TYPE(RTCAudioEngineAvailability); RTC_EXTERN NSString *const RTC_CONSTANT_TYPE(RTCAudioEngineInputMixerNodeKey);