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;