-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathaudiounitbackend.mm
More file actions
150 lines (123 loc) · 5.62 KB
/
Copy pathaudiounitbackend.mm
File metadata and controls
150 lines (123 loc) · 5.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include "effects/backends/audiounit/audiounitbackend.h"
#import <AVFAudio/AVFAudio.h>
#import <AudioToolbox/AudioToolbox.h>
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
#include <QDebug>
#include <QHash>
#include <QList>
#include <QMutex>
#include <QString>
#include <memory>
#include "effects/backends/audiounit/audiounitbackend.h"
#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([NSMutableDictionary dictionary]) {
loadAudioUnits();
}
~AudioUnitBackend() override {
}
EffectBackendType getType() const override {
return EffectBackendType::AudioUnit;
};
const QList<QString> getEffectIds() const override {
// Only report effects whose manifest actually finished loading.
// If loadAudioUnitsOfType() timed out, some components in
// m_componentsById may not have a corresponding manifest yet, and
// callers would get a null EffectManifestPointer from getManifest(),
// which crashes downstream (e.g. EffectManifest::sortLexigraphically).
auto locker = lockMutex(&m_mutex);
return m_manifestsById.keys();
}
EffectManifestPointer getManifest(const QString& effectId) const override {
auto locker = lockMutex(&m_mutex);
return m_manifestsById.value(effectId);
}
const QList<EffectManifestPointer> getManifests() const override {
auto locker = lockMutex(&m_mutex);
return m_manifestsById.values();
}
bool canInstantiateEffect(const QString& effectId) const override {
return [m_componentsById objectForKey:effectId.toNSString()] != nil;
}
std::unique_ptr<EffectProcessor> createProcessor(
const EffectManifestPointer pManifest) const override {
AVAudioUnitComponent* component =
m_componentsById[pManifest->id().toNSString()];
return std::make_unique<AudioUnitEffectProcessor>(component);
}
private:
NSMutableDictionary<NSString*, AVAudioUnitComponent*>* m_componentsById;
QHash<QString, EffectManifestPointer> m_manifestsById;
mutable QMutex m_mutex;
void loadAudioUnits() {
qDebug() << "Loading audio units...";
// See
// https://developer.apple.com/documentation/audiotoolbox/audio_unit_v3_plug-ins/incorporating_audio_effects_and_instruments?language=objc
// Discover all AU components of both types first, then load all
// manifests in a single parallel batch. This avoids the performance
// penalty of two sequential discovery passes each with their own
// blocking wait.
auto manager =
[AVAudioUnitComponentManager sharedAudioUnitComponentManager];
NSMutableArray<AVAudioUnitComponent*>* allComponents =
[NSMutableArray array];
for (OSType componentType :
{kAudioUnitType_Effect, kAudioUnitType_MusicEffect}) {
AudioComponentDescription description = {
.componentType = componentType,
.componentSubType = 0,
.componentManufacturer = 0,
.componentFlags = 0,
.componentFlagsMask = 0,
};
auto components =
[manager componentsMatchingDescription:description];
[allComponents addObjectsFromArray:components];
}
// 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 allComponents) {
qDebug() << "Found audio unit" << [component name];
QString effectId = QString::fromNSString(
[NSString stringWithFormat:@"%@~%@~%@",
[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;
});
}
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";
}
}
};
EffectsBackendPointer createAudioUnitBackend() {
return EffectsBackendPointer(new AudioUnitBackend());
}