Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 6 additions & 1 deletion modules/audio_device/audio_engine_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ enum AudioEngineErrorCode {

// Voice processing errors
kAudioEngineVoiceProcessingError = -8000,
kAudioEngineAGCError = -8001
kAudioEngineAGCError = -8001,

// Permission and session errors
kAudioEngineErrorInsufficientDevicePermission = -9000,
kAudioEngineErrorAudioSessionCategoryRecordingRequired = -9001
};

class FineAudioBuffer;
Expand Down Expand Up @@ -414,6 +418,7 @@ class AudioEngineDevice : public AudioDeviceModule, public AudioSessionObserver
EngineState engine_state_ RTC_GUARDED_BY(thread_);

bool IsMicrophonePermissionGranted();
bool EnsureMicrophonePermissionSync();
int32_t ModifyEngineState(std::function<EngineState(EngineState)> state_transform);

int32_t ApplyDeviceEngineState(EngineStateUpdate state);
Expand Down
53 changes: 53 additions & 0 deletions modules/audio_device/audio_engine_device.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,33 @@
return status == AVAuthorizationStatusAuthorized;
}

bool AudioEngineDevice::EnsureMicrophonePermissionSync() {
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];

if (status == AVAuthorizationStatusAuthorized) {
return true;
}

if (status == AVAuthorizationStatusNotDetermined) {
// Request permission synchronously - this will block WebRTC's worker thread
// but this is acceptable since instantiating AVAudioInputNode would block anyway
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block BOOL granted = NO;

[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio
completionHandler:^(BOOL granted_inner) {
granted = granted_inner;
dispatch_semaphore_signal(semaphore);
}];

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return granted;
}

// Status is denied or restricted
return false;
}

int32_t AudioEngineDevice::ModifyEngineState(
std::function<EngineState(EngineState)> state_transform) {
RTC_DCHECK_RUN_ON(thread_);
Expand Down Expand Up @@ -1785,6 +1812,32 @@
// Step: Trigger "engine will enable" event
//
if (state.DidAnyEnable() && observer_ != nullptr) {
// Safety checks for device rendering mode with recording enabled
// At this point mic permissions / session should be configured for recording.
if (state.next.IsInputEnabled() && state.next.render_mode == RenderMode::Device) {
// Attempt to acquire mic permissions at this point to return an erorr early.
bool isAuthorized = EnsureMicrophonePermissionSync();
LOGI() << "AudioEngine pre-enable check, device permission: "
<< (isAuthorized ? "true" : "false");
if (!isAuthorized) {
return rollback(kAudioEngineErrorInsufficientDevicePermission);
}

#if !TARGET_OS_OSX
// Additional check for audio session category on non-macOS platforms.
AVAudioSession* session = [AVAudioSession sharedInstance];
NSString* category = session.category;

// Check if category supports recording
bool isCategoryValid = [category isEqualToString:AVAudioSessionCategoryPlayAndRecord] ||
[category isEqualToString:AVAudioSessionCategoryRecord];
LOGI() << "AudioEngine pre-enable check, audio session category: " << category.UTF8String;
if (!isCategoryValid) {
return rollback(kAudioEngineErrorAudioSessionCategoryRecordingRequired);
}
#endif
}

// Invoke here before configuring nodes. In iOS, session configuration is required before
// enabling AGC, muted talker etc.
int32_t result = observer_->OnEngineWillEnable(engine_device_, state.next.IsOutputEnabled(),
Expand Down