diff --git a/src/effects/backends/audiounit/audiounitbackend.mm b/src/effects/backends/audiounit/audiounitbackend.mm index 8e7b839b259..a06e5491db9 100644 --- a/src/effects/backends/audiounit/audiounitbackend.mm +++ b/src/effects/backends/audiounit/audiounitbackend.mm @@ -3,10 +3,12 @@ #import #import #import +#import #include #include #include +#include #include #include @@ -14,11 +16,12 @@ #include "effects/backends/audiounit/audiouniteffectprocessor.h" #include "effects/backends/audiounit/audiounitmanifest.h" #include "effects/defs.h" +#include "util/compatibility/qmutex.h" /// An effects backend for Audio Unit (AU) plugins. macOS-only. class AudioUnitBackend : public EffectsBackend { public: - AudioUnitBackend() : m_componentsById([[NSDictionary alloc] init]) { + AudioUnitBackend() : m_componentsById([[NSMutableDictionary alloc] init]) { loadAudioUnits(); } @@ -59,18 +62,24 @@ bool canInstantiateEffect(const QString& effectId) const override { } private: - NSDictionary* m_componentsById; + NSMutableDictionary* m_componentsById; QHash m_manifestsById; + QMutex m_mutex; void loadAudioUnits() { qDebug() << "Loading audio units..."; + loadAudioUnitsOfType(kAudioUnitType_Effect); + loadAudioUnitsOfType(kAudioUnitType_MusicEffect); + } + + void loadAudioUnitsOfType(OSType componentType) { // See // https://developer.apple.com/documentation/audiotoolbox/audio_unit_v3_plug-ins/incorporating_audio_effects_and_instruments?language=objc // Create a query for audio components AudioComponentDescription description = { - .componentType = kAudioUnitType_Effect, + .componentType = componentType, .componentSubType = 0, .componentManufacturer = 0, .componentFlags = 0, @@ -78,32 +87,55 @@ void loadAudioUnits() { }; // Find the audio units - // TODO: Should we perform this asynchronously (e.g. using Qt's - // threading or GCD)? auto manager = [AVAudioUnitComponentManager sharedAudioUnitComponentManager]; auto components = [manager componentsMatchingDescription:description]; - // Assign ids to the components - NSMutableDictionary* componentsById = - [[NSMutableDictionary alloc] init]; - QHash manifestsById; + // Load component manifests (parameters etc.) concurrently since this + // requires instantiating the corresponding Audio Units. We use Grand + // Central Dispatch (GCD) for this instead of Qt's threading facilities + // since GCD is a bit more lightweight and generally preferred for + // Apple API-related stuff. + + dispatch_group_t group = dispatch_group_create(); for (AVAudioUnitComponent* component in components) { qDebug() << "Found audio unit" << [component name]; QString effectId = QString::fromNSString( [NSString stringWithFormat:@"%@~%@~%@", - [component manufacturerName], - [component name], - [component versionString]]); - componentsById[effectId.toNSString()] = component; - manifestsById[effectId] = EffectManifestPointer( - new AudioUnitManifest(effectId, component)); + [component manufacturerName], + [component name], + [component versionString]]); + + // Register component + m_componentsById[effectId.toNSString()] = component; + + // Use a concurrent background GCD queue to load manifest + dispatch_queue_t queue = dispatch_get_global_queue( + DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + dispatch_group_async(group, queue, ^{ + // Load manifest (potentially slow blocking operation) + auto manifest = EffectManifestPointer( + new AudioUnitManifest(effectId, component)); + + // Register manifest + auto locker = lockMutex(&m_mutex); + m_manifestsById[effectId] = manifest; + }); } - m_componentsById = componentsById; - m_manifestsById = manifestsById; + const int64_t TIMEOUT_MS = 6000; + + qDebug() << "Waiting for audio unit manifests to be loaded..."; + if (dispatch_group_wait(group, + dispatch_time(DISPATCH_TIME_NOW, TIMEOUT_MS * 1000000)) == + 0) { + qDebug() << "Successfully loaded audio unit manifests"; + } else { + qWarning() << "Timed out while loading audio unit manifests"; + } } }; diff --git a/src/effects/backends/audiounit/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm index 1ee8fd2c8f2..dda59e053ba 100644 --- a/src/effects/backends/audiounit/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -1,6 +1,8 @@ #import #include "effects/backends/effectmanifestparameter.h" +#include +#include #include #include "effects/backends/audiounit/audiounitmanager.h" @@ -18,9 +20,25 @@ setDescription(QString::fromNSString([component typeName])); setAuthor(QString::fromNSString([component manufacturerName])); - // Try instantiating the unit in-process to fetch its properties quickly + // Instantiate audio unit (out-of-process) to load parameters + AudioUnitManager manager{component}; + + const int TIMEOUT_MS = 2000; + + QElapsedTimer timer; + timer.start(); + + while (manager.getAudioUnit() == nil) { + if (timer.elapsed() > TIMEOUT_MS) { + qWarning() << name() << "took more than" << TIMEOUT_MS + << "ms to initialize, skipping manifest initialization " + "for this effect. This means this effect will not " + "display any parameters and likely not be useful!"; + return; + } + QThread::msleep(10); + } - AudioUnitManager manager{component, AudioUnitInstantiationType::Sync}; AudioUnit audioUnit = manager.getAudioUnit(); if (audioUnit) {