Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/objc/native/src/audio/audio_device_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) override;
void OnRunningStateChanged() override;

bool IsInterrupted();

Expand All @@ -171,6 +172,7 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
void HandleSampleRateChange();
void HandlePlayoutGlitchDetected();
void HandleOutputVolumeChange();
void HandleAudioUnitRunningStateChange();

// Uses current `playout_parameters_` and `record_parameters_` to inform the
// audio device buffer (ADB) about our internal audio parameters.
Expand Down
24 changes: 24 additions & 0 deletions sdk/objc/native/src/audio/audio_device_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,11 @@ static void LogDeviceInfo() {
thread_->PostTask(SafeTask(safety_, [this] { HandleValidRouteChange(); }));
}

void AudioDeviceIOS::OnRunningStateChanged() {
RTC_DCHECK(thread_);
thread_->PostTask(SafeTask(safety_, [this] { HandleAudioUnitRunningStateChange(); }));
}

void AudioDeviceIOS::OnCanPlayOrRecordChange(bool can_play_or_record) {
RTC_DCHECK(thread_);
thread_->PostTask(SafeTask(
Expand Down Expand Up @@ -549,6 +554,25 @@ static void LogDeviceInfo() {
HandleSampleRateChange();
}

void AudioDeviceIOS::HandleAudioUnitRunningStateChange() {
RTC_DCHECK_RUN_ON(thread_);
// The listener fires on every start and stop, including WebRTC's own and the
// restart below; those runs return here. Only a unit marked as started that
// isn't running was stopped by someone else, e.g. other code deactivating the
// audio session.
if (!audio_unit_ || audio_unit_->GetState() != VoiceProcessingAudioUnit::kStarted ||
audio_unit_->IsRunning()) {
return;
}
if (!audio_unit_->Stop()) {
RTCLogError(@"Failed to stop the externally stopped audio unit. Not restarting it.");
return;
}
PrepareForNewStart();
RTCLog(@"Audio unit was stopped externally. Restarting it.");
UpdateAudioUnit([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance].canPlayOrRecord);
}

void AudioDeviceIOS::HandleCanPlayOrRecordChange(bool can_play_or_record) {
RTCLog(@"Handling CanPlayOrRecord change to: %d", can_play_or_record);
UpdateAudioUnit(can_play_or_record);
Expand Down
10 changes: 10 additions & 0 deletions sdk/objc/native/src/audio/voice_processing_audio_unit.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class VoiceProcessingAudioUnitObserver {
UInt32 num_frames,
AudioBufferList* io_data) = 0;

virtual void OnRunningStateChanged() = 0;

protected:
~VoiceProcessingAudioUnitObserver() {}
};
Expand Down Expand Up @@ -77,6 +79,8 @@ class VoiceProcessingAudioUnit {

VoiceProcessingAudioUnit::State GetState() const;

bool IsRunning() const;

// Initializes the underlying audio unit with the given sample rate.
// When enable_input is false, the input bus is disabled so iOS does not
// request microphone permission and the orange mic indicator stays off.
Expand Down Expand Up @@ -115,6 +119,12 @@ class VoiceProcessingAudioUnit {
UInt32 num_frames,
AudioBufferList* io_data);

static void OnIsRunningChanged(void* in_ref_con,
AudioUnit audio_unit,
AudioUnitPropertyID property_id,
AudioUnitScope scope,
AudioUnitElement element);

// Notifies observer that samples are needed for playback.
OSStatus NotifyGetPlayoutData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
Expand Down
29 changes: 29 additions & 0 deletions sdk/objc/native/src/audio/voice_processing_audio_unit.mm
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
return false;
}

result = AudioUnitAddPropertyListener(
vpio_unit_, kAudioOutputUnitProperty_IsRunning, OnIsRunningChanged, observer_);
if (result != noErr) {
RTCLogError(@"Failed to add the IsRunning listener. Error=%ld.", (long)result);
}

// Enable (or leave disabled) input on the input scope of the input element.
// CRITICAL: setting EnableIO=1 on a Voice Processing I/O input bus causes
// iOS (via AURemoteIO::SetProperty → AudioSessionRequestRecordPermission)
Expand Down Expand Up @@ -374,6 +380,18 @@ static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
return noErr;
}

bool VoiceProcessingAudioUnit::IsRunning() const {
UInt32 is_running = 0;
UInt32 size = sizeof(is_running);
OSStatus result = AudioUnitGetProperty(vpio_unit_,
kAudioOutputUnitProperty_IsRunning,
kAudioUnitScope_Global,
0,
&is_running,
&size);
return result == noErr && is_running != 0;
}

bool VoiceProcessingAudioUnit::Stop() {
RTC_DCHECK_GE(state_, kUninitialized);
RTCLog(@"Stopping audio unit.");
Expand Down Expand Up @@ -447,6 +465,14 @@ static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
num_frames, io_data);
}

void VoiceProcessingAudioUnit::OnIsRunningChanged(void* in_ref_con,
AudioUnit audio_unit,
AudioUnitPropertyID property_id,
AudioUnitScope scope,
AudioUnitElement element) {
static_cast<VoiceProcessingAudioUnitObserver*>(in_ref_con)->OnRunningStateChanged();
}

OSStatus VoiceProcessingAudioUnit::NotifyGetPlayoutData(
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
Expand Down Expand Up @@ -502,6 +528,9 @@ static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
break;
}

AudioUnitRemovePropertyListenerWithUserData(
vpio_unit_, kAudioOutputUnitProperty_IsRunning, OnIsRunningChanged, observer_);

RTCLog(@"Disposing audio unit.");
OSStatus result = AudioComponentInstanceDispose(vpio_unit_);
if (result != noErr) {
Expand Down