From c8609f4c0f102300c3b66aa731397ca4cb3a355f Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:02:10 +0200 Subject: [PATCH] [macOS/iOS] Use hardware sampling rates for audio I/O. --- doc/classes/AudioServer.xml | 6 ++ drivers/SCsub | 3 +- drivers/coreaudio/SCsub | 2 +- drivers/coreaudio/audio_driver_coreaudio.h | 5 ++ ...oreaudio.cpp => audio_driver_coreaudio.mm} | 69 +++++++++++++++---- servers/audio/audio_stream.cpp | 4 +- servers/audio_server.cpp | 5 ++ servers/audio_server.h | 2 + 8 files changed, 78 insertions(+), 18 deletions(-) rename drivers/coreaudio/{audio_driver_coreaudio.cpp => audio_driver_coreaudio.mm} (88%) diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index 6830c632cf9..9ff8125e9d6 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -117,6 +117,12 @@ [b]Note:[/b] [member ProjectSettings.audio/driver/enable_input] must be [code]true[/code] for audio input to work. See also that setting's description for caveats related to permissions and operating system privacy settings. + + + + Returns the sample rate at the input of the [AudioServer]. + + diff --git a/drivers/SCsub b/drivers/SCsub index e0bfa138f58..03ad70649b2 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -14,7 +14,8 @@ SConscript("windows/SCsub") # Sounds drivers SConscript("alsa/SCsub") -SConscript("coreaudio/SCsub") +if env["platform"] == "ios" or env["platform"] == "macos": + SConscript("coreaudio/SCsub") SConscript("pulseaudio/SCsub") if env["platform"] == "windows": SConscript("wasapi/SCsub") diff --git a/drivers/coreaudio/SCsub b/drivers/coreaudio/SCsub index 69d667c57bf..83ac27f4b61 100644 --- a/drivers/coreaudio/SCsub +++ b/drivers/coreaudio/SCsub @@ -4,4 +4,4 @@ from misc.utility.scons_hints import * Import("env") # Driver source files -env.add_source_files(env.drivers_sources, "*.cpp") +env.add_source_files(env.drivers_sources, "*.mm") diff --git a/drivers/coreaudio/audio_driver_coreaudio.h b/drivers/coreaudio/audio_driver_coreaudio.h index 67ff3f3efc6..b2b89c35c5b 100644 --- a/drivers/coreaudio/audio_driver_coreaudio.h +++ b/drivers/coreaudio/audio_driver_coreaudio.h @@ -38,6 +38,8 @@ #import #ifdef MACOS_ENABLED #import +#else +#import #endif class AudioDriverCoreAudio : public AudioDriver { @@ -51,9 +53,11 @@ class AudioDriverCoreAudio : public AudioDriver { String input_device_name = "Default"; int mix_rate = 0; + int capture_mix_rate = 0; unsigned int channels = 2; unsigned int capture_channels = 2; unsigned int buffer_frames = 0; + unsigned int capture_buffer_frames = 0; Vector samples_in; Vector input_buf; @@ -94,6 +98,7 @@ public: virtual Error init() override; virtual void start() override; virtual int get_mix_rate() const override; + virtual int get_input_mix_rate() const override; virtual SpeakerMode get_speaker_mode() const override; virtual void lock() override; diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.mm similarity index 88% rename from drivers/coreaudio/audio_driver_coreaudio.cpp rename to drivers/coreaudio/audio_driver_coreaudio.mm index 433bbfb3f59..4e6ff2edb39 100644 --- a/drivers/coreaudio/audio_driver_coreaudio.cpp +++ b/drivers/coreaudio/audio_driver_coreaudio.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* audio_driver_coreaudio.cpp */ +/* audio_driver_coreaudio.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -121,7 +121,24 @@ Error AudioDriverCoreAudio::init() { break; } - mix_rate = _get_configured_mix_rate(); +#ifdef MACOS_ENABLED + AudioDeviceID device_id; + UInt32 dev_id_size = sizeof(AudioDeviceID); + + AudioObjectPropertyAddress property_dev_id = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property_dev_id, 0, nullptr, &dev_id_size, &device_id); + ERR_FAIL_COND_V(result != noErr, FAILED); + + double hw_mix_rate; + UInt32 hw_mix_rate_size = sizeof(hw_mix_rate); + + AudioObjectPropertyAddress property_sr = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; + result = AudioObjectGetPropertyData(device_id, &property_sr, 0, nullptr, &hw_mix_rate_size, &hw_mix_rate); + ERR_FAIL_COND_V(result != noErr, FAILED); +#else + double hw_mix_rate = [AVAudioSession sharedInstance].sampleRate; +#endif + mix_rate = hw_mix_rate; memset(&strdesc, 0, sizeof(strdesc)); strdesc.mFormatID = kAudioFormatLinearPCM; @@ -147,10 +164,10 @@ Error AudioDriverCoreAudio::init() { unsigned int buffer_size = buffer_frames * channels; samples_in.resize(buffer_size); - input_buf.resize(buffer_size); print_verbose("CoreAudio: detected " + itos(channels) + " channels"); - print_verbose("CoreAudio: audio buffer frames: " + itos(buffer_frames) + " calculated latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms"); + print_verbose("CoreAudio: output sampling rate: " + itos(mix_rate) + " Hz"); + print_verbose("CoreAudio: output audio buffer frames: " + itos(buffer_frames) + " calculated latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms"); AURenderCallbackStruct callback; memset(&callback, 0, sizeof(AURenderCallbackStruct)); @@ -275,6 +292,10 @@ int AudioDriverCoreAudio::get_mix_rate() const { return mix_rate; } +int AudioDriverCoreAudio::get_input_mix_rate() const { + return capture_mix_rate; +} + AudioDriver::SpeakerMode AudioDriverCoreAudio::get_speaker_mode() const { return get_speaker_mode_by_total_channels(channels); } @@ -378,14 +399,14 @@ Error AudioDriverCoreAudio::init_input_device() { UInt32 size; #ifdef MACOS_ENABLED - AudioDeviceID deviceId; + AudioDeviceID device_id; size = sizeof(AudioDeviceID); AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; - result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, nullptr, &size, &deviceId); + result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, nullptr, &size, &device_id); ERR_FAIL_COND_V(result != noErr, FAILED); - result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceId, sizeof(AudioDeviceID)); + result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &device_id, sizeof(AudioDeviceID)); ERR_FAIL_COND_V(result != noErr, FAILED); #endif @@ -410,13 +431,23 @@ Error AudioDriverCoreAudio::init_input_device() { break; } - mix_rate = _get_configured_mix_rate(); +#ifdef MACOS_ENABLED + double hw_mix_rate; + UInt32 hw_mix_rate_size = sizeof(hw_mix_rate); + + AudioObjectPropertyAddress property_sr = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; + result = AudioObjectGetPropertyData(device_id, &property_sr, 0, nullptr, &hw_mix_rate_size, &hw_mix_rate); + ERR_FAIL_COND_V(result != noErr, FAILED); +#else + double hw_mix_rate = [AVAudioSession sharedInstance].sampleRate; +#endif + capture_mix_rate = hw_mix_rate; memset(&strdesc, 0, sizeof(strdesc)); strdesc.mFormatID = kAudioFormatLinearPCM; strdesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; strdesc.mChannelsPerFrame = capture_channels; - strdesc.mSampleRate = mix_rate; + strdesc.mSampleRate = capture_mix_rate; strdesc.mFramesPerPacket = 1; strdesc.mBitsPerChannel = 16; strdesc.mBytesPerFrame = strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8; @@ -425,6 +456,13 @@ Error AudioDriverCoreAudio::init_input_device() { result = AudioUnitSetProperty(input_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &strdesc, sizeof(strdesc)); ERR_FAIL_COND_V(result != noErr, FAILED); + int latency = Engine::get_singleton()->get_audio_output_latency(); + // 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 * capture_mix_rate / 1000); + + unsigned int buffer_size = capture_buffer_frames * capture_channels; + input_buf.resize(buffer_size); + AURenderCallbackStruct callback; memset(&callback, 0, sizeof(AURenderCallbackStruct)); callback.inputProc = &AudioDriverCoreAudio::input_callback; @@ -435,6 +473,9 @@ Error AudioDriverCoreAudio::init_input_device() { result = AudioUnitInitialize(input_unit); ERR_FAIL_COND_V(result != noErr, FAILED); + print_verbose("CoreAudio: input sampling rate: " + itos(capture_mix_rate) + " Hz"); + print_verbose("CoreAudio: input audio buffer frames: " + itos(capture_buffer_frames) + " calculated latency: " + itos(capture_buffer_frames * 1000 / capture_mix_rate) + "ms"); + return OK; } @@ -477,7 +518,7 @@ void AudioDriverCoreAudio::finish_input_device() { } Error AudioDriverCoreAudio::input_start() { - input_buffer_init(buffer_frames); + input_buffer_init(capture_buffer_frames); OSStatus result = AudioOutputUnitStart(input_unit); if (result != noErr) { @@ -561,7 +602,7 @@ PackedStringArray AudioDriverCoreAudio::_get_device_list(bool input) { } void AudioDriverCoreAudio::_set_device(const String &output_device, bool input) { - AudioDeviceID deviceId; + AudioDeviceID device_id; bool found = false; if (output_device != "Default") { AudioObjectPropertyAddress prop; @@ -608,7 +649,7 @@ void AudioDriverCoreAudio::_set_device(const String &output_device, bool input) if (CFStringGetCString(cfname, buffer, maxSize, kCFStringEncodingUTF8)) { String name = String::utf8(buffer) + " (" + itos(audioDevices[i]) + ")"; if (name == output_device) { - deviceId = audioDevices[i]; + device_id = audioDevices[i]; found = true; } } @@ -626,14 +667,14 @@ void AudioDriverCoreAudio::_set_device(const String &output_device, bool input) UInt32 elem = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice; AudioObjectPropertyAddress property = { elem, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; - OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, nullptr, &size, &deviceId); + OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, nullptr, &size, &device_id); ERR_FAIL_COND(result != noErr); found = true; } if (found) { - OSStatus result = AudioUnitSetProperty(input ? input_unit : audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceId, sizeof(AudioDeviceID)); + OSStatus result = AudioUnitSetProperty(input ? input_unit : audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &device_id, sizeof(AudioDeviceID)); ERR_FAIL_COND(result != noErr); if (input) { diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index d400b5790f4..42aa43269bc 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -390,7 +390,7 @@ int AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fra Vector buf = AudioDriver::get_singleton()->get_input_buffer(); unsigned int input_size = AudioDriver::get_singleton()->get_input_size(); - int mix_rate = AudioDriver::get_singleton()->get_mix_rate(); + int mix_rate = AudioDriver::get_singleton()->get_input_mix_rate(); unsigned int playback_delay = MIN(((50 * mix_rate) / 1000) * 2, buf.size() >> 1); #ifdef DEBUG_ENABLED unsigned int input_position = AudioDriver::get_singleton()->get_input_position(); @@ -441,7 +441,7 @@ int AudioStreamPlaybackMicrophone::mix(AudioFrame *p_buffer, float p_rate_scale, } float AudioStreamPlaybackMicrophone::get_stream_sampling_rate() { - return AudioDriver::get_singleton()->get_mix_rate(); + return AudioDriver::get_singleton()->get_input_mix_rate(); } void AudioStreamPlaybackMicrophone::start(double p_from_pos) { diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 70ef88e36dd..f68281c3f95 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -1614,6 +1614,10 @@ float AudioServer::get_mix_rate() const { return AudioDriver::get_singleton()->get_mix_rate(); } +float AudioServer::get_input_mix_rate() const { + return AudioDriver::get_singleton()->get_input_mix_rate(); +} + float AudioServer::read_output_peak_db() const { return 0; } @@ -1946,6 +1950,7 @@ void AudioServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_speaker_mode"), &AudioServer::get_speaker_mode); ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioServer::get_mix_rate); + ClassDB::bind_method(D_METHOD("get_input_mix_rate"), &AudioServer::get_input_mix_rate); ClassDB::bind_method(D_METHOD("get_output_device_list"), &AudioServer::get_output_device_list); ClassDB::bind_method(D_METHOD("get_output_device"), &AudioServer::get_output_device); diff --git a/servers/audio_server.h b/servers/audio_server.h index 16fcc029b3a..f1b4eabd38c 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -99,6 +99,7 @@ public: virtual Error init() = 0; virtual void start() = 0; virtual int get_mix_rate() const = 0; + virtual int get_input_mix_rate() const { return get_mix_rate(); } virtual SpeakerMode get_speaker_mode() const = 0; virtual float get_latency() { return 0; } @@ -441,6 +442,7 @@ public: virtual SpeakerMode get_speaker_mode() const; virtual float get_mix_rate() const; + virtual float get_input_mix_rate() const; virtual float read_output_peak_db() const;