Add permission request for Apple embedded platforms, fix microphone input

Co-Authored-By: Miguel de Icaza <miguel@gnome.org>

Supersedes https://github.com/godotengine/godot/pull/107233
Fixes https://github.com/godotengine/godot-proposals/issues/12563
Fixes https://github.com/godotengine/godot/issues/33885

Superseding Miguel's PR to get it in during the beta stage.
This commit is contained in:
Patrick Exner 2025-06-25 15:00:19 +02:00
parent 30456ba095
commit 5a7b6b7159
5 changed files with 44 additions and 10 deletions

View file

@ -302,6 +302,7 @@
<description> <description>
On Android devices: Returns the list of dangerous permissions that have been granted. On Android devices: Returns the list of dangerous permissions that have been granted.
On macOS: Returns the list of granted permissions and user selected folders accessible to the application (sandboxed applications only). Use the native file dialog to request folder access permission. On macOS: Returns the list of granted permissions and user selected folders accessible to the application (sandboxed applications only). Use the native file dialog to request folder access permission.
On iOS, visionOS: Returns the list of granted permissions.
</description> </description>
</method> </method>
<method name="get_keycode_string" qualifiers="const"> <method name="get_keycode_string" qualifiers="const">
@ -781,8 +782,9 @@
- [code]OS.request_permission("android.permission.READ_EXTERNAL_STORAGE")[/code] - [code]OS.request_permission("android.permission.READ_EXTERNAL_STORAGE")[/code]
- [code]OS.request_permission("android.permission.POST_NOTIFICATIONS")[/code] - [code]OS.request_permission("android.permission.POST_NOTIFICATIONS")[/code]
- [code]OS.request_permission("macos.permission.RECORD_SCREEN")[/code] - [code]OS.request_permission("macos.permission.RECORD_SCREEN")[/code]
- [code]OS.request_permission("appleembedded.permission.AUDIO_RECORD")[/code]
[b]Note:[/b] On Android, permission must be checked during export. [b]Note:[/b] On Android, permission must be checked during export.
[b]Note:[/b] This method is implemented on Android and macOS. [b]Note:[/b] This method is implemented on Android, macOS, and visionOS platforms.
</description> </description>
</method> </method>
<method name="request_permissions"> <method name="request_permissions">

View file

@ -137,6 +137,9 @@ public:
void on_exit_background(); void on_exit_background();
virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override; virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override;
virtual bool request_permission(const String &p_name) override;
virtual Vector<String> get_granted_permissions() const override;
}; };
#endif // APPLE_EMBEDDED_ENABLED #endif // APPLE_EMBEDDED_ENABLED

View file

@ -43,6 +43,7 @@
#import "drivers/apple/os_log_logger.h" #import "drivers/apple/os_log_logger.h"
#include "main/main.h" #include "main/main.h"
#import <AVFoundation/AVFAudio.h>
#import <AudioToolbox/AudioServices.h> #import <AudioToolbox/AudioServices.h>
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@ -733,4 +734,35 @@ Rect2 OS_AppleEmbedded::calculate_boot_screen_rect(const Size2 &p_window_size, c
} }
} }
bool OS_AppleEmbedded::request_permission(const String &p_name) {
if (p_name == "appleembedded.permission.AUDIO_RECORD") {
if (@available(iOS 17.0, *)) {
AVAudioApplicationRecordPermission permission = [AVAudioApplication sharedInstance].recordPermission;
if (permission == AVAudioApplicationRecordPermissionGranted) {
// Permission already granted, you can start recording.
return true;
} else if (permission == AVAudioApplicationRecordPermissionDenied) {
// Permission denied, or not yet granted.
return false;
} else {
// Request the permission, but for now return false as documented.
[AVAudioApplication requestRecordPermissionWithCompletionHandler:^(BOOL granted) {
get_main_loop()->emit_signal(SNAME("on_request_permissions_result"), p_name, granted);
}];
}
}
}
return false;
}
Vector<String> OS_AppleEmbedded::get_granted_permissions() const {
Vector<String> ret;
if (@available(iOS 17.0, *)) {
if ([AVAudioApplication sharedInstance].recordPermission == AVAudioApplicationRecordPermissionGranted) {
ret.push_back("appleembedded.permission.AUDIO_RECORD");
}
}
return ret;
}
#endif // APPLE_EMBEDDED_ENABLED #endif // APPLE_EMBEDDED_ENABLED

View file

@ -59,7 +59,7 @@ class AudioDriverCoreAudio : public AudioDriver {
unsigned int capture_buffer_frames = 0; unsigned int capture_buffer_frames = 0;
Vector<int32_t> samples_in; Vector<int32_t> samples_in;
Vector<int16_t> input_buf; unsigned int buffer_size = 0;
#ifdef MACOS_ENABLED #ifdef MACOS_ENABLED
PackedStringArray _get_device_list(bool capture = false); PackedStringArray _get_device_list(bool capture = false);

View file

@ -241,14 +241,15 @@ OSStatus AudioDriverCoreAudio::input_callback(void *inRefCon,
AudioBufferList bufferList; AudioBufferList bufferList;
bufferList.mNumberBuffers = 1; bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mData = ad->input_buf.ptrw(); bufferList.mBuffers[0].mData = nullptr;
bufferList.mBuffers[0].mNumberChannels = ad->capture_channels; bufferList.mBuffers[0].mNumberChannels = ad->capture_channels;
bufferList.mBuffers[0].mDataByteSize = ad->input_buf.size() * sizeof(int16_t); bufferList.mBuffers[0].mDataByteSize = ad->buffer_size * sizeof(int16_t);
OSStatus result = AudioUnitRender(ad->input_unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList); OSStatus result = AudioUnitRender(ad->input_unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList);
if (result == noErr) { if (result == noErr) {
int16_t *data = (int16_t *)bufferList.mBuffers[0].mData;
for (unsigned int i = 0; i < inNumberFrames * ad->capture_channels; i++) { for (unsigned int i = 0; i < inNumberFrames * ad->capture_channels; i++) {
int32_t sample = ad->input_buf[i] << 16; int32_t sample = data[i] << 16;
ad->input_buffer_write(sample); ad->input_buffer_write(sample);
if (ad->capture_channels == 1) { if (ad->capture_channels == 1) {
@ -393,9 +394,6 @@ Error AudioDriverCoreAudio::init_input_device() {
UInt32 flag = 1; UInt32 flag = 1;
result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag)); result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
ERR_FAIL_COND_V(result != noErr, FAILED); ERR_FAIL_COND_V(result != noErr, FAILED);
flag = 0;
result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
ERR_FAIL_COND_V(result != noErr, FAILED);
UInt32 size; UInt32 size;
#ifdef MACOS_ENABLED #ifdef MACOS_ENABLED
@ -460,8 +458,7 @@ Error AudioDriverCoreAudio::init_input_device() {
// Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels) // Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels)
capture_buffer_frames = closest_power_of_2(latency * (uint32_t)capture_mix_rate / (uint32_t)1000); capture_buffer_frames = closest_power_of_2(latency * (uint32_t)capture_mix_rate / (uint32_t)1000);
unsigned int buffer_size = capture_buffer_frames * capture_channels; buffer_size = capture_buffer_frames * capture_channels;
input_buf.resize(buffer_size);
AURenderCallbackStruct callback; AURenderCallbackStruct callback;
memset(&callback, 0, sizeof(AURenderCallbackStruct)); memset(&callback, 0, sizeof(AURenderCallbackStruct));