forked from bitgapp/eqMac
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEQMInterface.swift
More file actions
498 lines (420 loc) · 20.1 KB
/
Copy pathEQMInterface.swift
File metadata and controls
498 lines (420 loc) · 20.1 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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
//
// EQMInterface.swift
// eqMac
//
// Created by Romans Kisils on 21/08/2021.
// Copyright © 2021 Bitgapp. All rights reserved.
//
import Foundation
import CoreAudio.AudioServerPlugIn
import Shared
// MARK: - Inheritance
func EQM_QueryInterface (
inDriver: UnsafeMutableRawPointer?,
inUUID: REFIID,
outInterface: UnsafeMutablePointer<LPVOID?>?
) -> HRESULT {
// This function is called by the HAL to get the interface to talk to the plug-in through.
// AudioServerPlugIns are required to support the IUnknown interface and the
// AudioServerPlugInDriverInterface. As it happens, all interfaces must also provide the
// IUnknown interface, so we can always just return the single interface we made with
// gAudioServerPlugInDriverInterfacePtr regardless of which one is asked for.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
// log("Invoked EQM_QueryInterface()")
let uuid = CFUUIDCreateFromUUIDBytes(nil, inUUID)
if uuid == nil || outInterface == nil {
return kAudioHardwareIllegalOperationError
}
guard CFEqual(uuid, IUnknownUUID) || CFEqual(uuid, kAudioServerPluginDriverInterfaceUUID) else { return HRESULT.noInterface }
EQMDriver.refCounter.wrappingIncrement(ordering: .relaxed)
outInterface!.pointee = UnsafeMutableRawPointer(EQMDriver.ref)
// log("EQM_QueryInterface() -> \(String(describing: EQMDriver.ref))")
return HRESULT.ok
}
func EQM_AddRef (inDriver: UnsafeMutableRawPointer?) -> ULONG {
// This call returns the resulting reference count after the increment.
guard EQMDriver.validateDriver(inDriver) else { return ULONG(kAudioHardwareBadObjectError) }
// log("Invoked EQM_AddRef()")
return EQMDriver.refCounter.wrappingIncrementThenLoad(ordering: .relaxed)
}
func EQM_Release (inDriver: UnsafeMutableRawPointer?) -> ULONG {
// This call returns the resulting reference count after the decrement.
guard EQMDriver.validateDriver(inDriver) else { return ULONG(kAudioHardwareBadObjectError) }
// log("Invoked EQM_Release()")
return EQMDriver.refCounter.wrappingDecrementThenLoad(ordering: .relaxed)
}
// MARK: - Basic Operations
func EQM_Initialize (inDriver: AudioServerPlugInDriverRef, inHost: AudioServerPlugInHostRef) -> OSStatus {
// This method is called to initialize the instance of the plug-in.
// The job of this method is, as the name implies, to get the driver initialized. One specific
// thing that needs to be done is to store the AudioServerPlugInHostRef so that it can be used
// later. Note that when this call returns, the HAL will scan the various lists the driver
// maintains (such as the device list) to get the inital set of objects the driver is
// publishing. So, there is no need to notifiy the HAL about any objects created as part of the
// execution of this method.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
// log("Invoked EQM_Initialize()")
EQMDriver.host = inHost
EQMDriver.calculateHostTicksPerFrame()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
if (!EQMClients.isAppClientPresent) {
EQMDevice.shown = false
}
}
// log("EQM_Initialize() - Host: \(String(describing: EQMDriver.host)) | hostTicksPerFrame = \(String(describing: EQMDriver.hostTicksPerFrame))")
return noErr
}
func EQM_CreateDevice (
inDriver: AudioServerPlugInDriverRef,
inDescription: CFDictionary,
inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>,
outDeviceObjectID: UnsafeMutablePointer<AudioObjectID>
) -> OSStatus {
// Tells the plug-in to create a new device based on the given description.
// This method is used to tell a driver that implements the Transport Manager semantics to
// create an AudioEndpointDevice from a set of AudioEndpoints. Since this driver is not a
// Transport Manager, we just check the arguments and return
// kAudioHardwareUnsupportedOperationError.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
return kAudioHardwareUnsupportedOperationError
}
func EQM_DestroyDevice (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID) -> OSStatus {
// Called to tell the plug-in about to destroy the given device.
// This method is used to tell a driver that implements the Transport Manager semantics to
// destroy an AudioEndpointDevice. Since this driver is not a Transport Manager, we just check
// the arguments and return kAudioHardwareUnsupportedOperationError.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
return kAudioHardwareUnsupportedOperationError
}
func EQM_AddDeviceClient (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>) -> OSStatus {
// Called to tell the plug-in about a new client of the Host for a particular device.
// This method is used to inform the driver about a new client that is using the given device.
// This allows the device to act differently depending on who the client is. This driver does
// not need to track the clients using the device, so we just check the arguments and return
// successfully.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
EQMClients.add(EQMClient(from: inClientInfo.pointee))
return noErr
}
func EQM_RemoveDeviceClient (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>) -> OSStatus {
// Called to tell the plug-in about a client that is no longer using the device.
// This method is used to inform the driver about a client that is no longer using the given
// device. This driver does not track clients, so we just check the arguments and return
// successfully.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
let client = EQMClient(from: inClientInfo.pointee)
EQMClients.remove(client)
if (client.isAppClient && !EQMClients.isAppClientPresent) {
EQMDevice.shown = false
}
return noErr
}
func EQM_PerformDeviceConfigurationChange (
inDriver: AudioServerPlugInDriverRef,
inDeviceObjectID: AudioObjectID,
inChangeAction: UInt64,
inChangeInfo: UnsafeMutableRawPointer?
) -> OSStatus {
// This is called by the Host to allow the device to perform a configuration change that had been previously requested via a call to the Host method, RequestDeviceConfigurationChange().
// This method is called to tell the device that it can perform the configuation change that it
// had requested via a call to the host method, RequestDeviceConfigurationChange(). The
// arguments, inChangeAction and inChangeInfo are the same as what was passed to
// RequestDeviceConfigurationChange().
//
// The HAL guarantees that IO will be stopped while this method is in progress. The HAL will
// also handle figuring out exactly what changed for the non-control related properties. This
// means that the only notifications that would need to be sent here would be for either
// custom properties the HAL doesn't know about or for controls.
//
// For the device implemented by this driver, only sample rate changes go through this process
// as it is the only state that can be changed for the device that isn't a control. For this
// change, the new sample rate is passed in the inChangeAction argument.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
// log("Invoked EQM_PerformDeviceConfigurationChange()")
if !kEQMDeviceSupportedSampleRates.contains(where: { UInt64($0) == inChangeAction }) { return kAudioHardwareBadObjectError }
EQMDriver.mutex.lock()
EQMDevice.sampleRate = Float64(inChangeAction)
EQMDriver.calculateHostTicksPerFrame()
EQMDriver.mutex.unlock()
// log("EQM_PerformDeviceConfigurationChange() - EQMDevice.sampleRate = \(EQMDevice.sampleRate) | hostTicksPerFrame = \(String(describing: EQMDriver.hostTicksPerFrame))")
return noErr
}
func EQM_AbortDeviceConfigurationChange (
inDriver: AudioServerPlugInDriverRef,
inDeviceObjectID: AudioObjectID,
inChangeAction: UInt64,
inChangeInfo: UnsafeMutableRawPointer?
) -> OSStatus {
// This is called by the Host to tell the plug-in not to perform a configuration change that had been requested via a call to the Host method, RequestDeviceConfigurationChange().
// This method is called to tell the driver that a request for a config change has been denied.
// This provides the driver an opportunity to clean up any state associated with the request.
// For this driver, an aborted config change requires no action. So we just check the arguments
// and return
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
return noErr
}
// MARK: - Property Operations
// Note that for each object, this driver implements all the required properties plus a few
// extras that are useful but not required. There is more detailed commentary about each
// property in the EQMPlugin.getPropertyData() and EQMDevice.getPropertyData() methods.
func EQM_HasProperty (
inDriver: AudioServerPlugInDriverRef,
inObjectID: AudioObjectID,
inClientProcessID: pid_t,
inAddress: UnsafePointer<AudioObjectPropertyAddress>
) -> DarwinBoolean {
// This method returns whether or not the given object has the given property.
let address = inAddress.pointee
// log("Invoking: \(EQMDriver.getEQMObjectClassName(from: inObjectID)).hasProperty(\(propertyName(address.mSelector)))")
let hasProperty: Bool = ({
guard EQMDriver.validateDriver(inDriver) else { return false }
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
return obj.hasProperty(objectID: inObjectID, address: address)
} else {
return false
}
})()
// log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).hasProperty(\(propertyName(address.mSelector)), \(scopeName(address.mScope))) = \(hasProperty)")
return DarwinBoolean(hasProperty)
}
func EQM_IsPropertySettable (
inDriver: AudioServerPlugInDriverRef,
inObjectID: AudioObjectID,
inClientProcessID: pid_t,
inAddress: UnsafePointer<AudioObjectPropertyAddress>,
outIsSettable: UnsafeMutablePointer<DarwinBoolean>
) -> OSStatus {
let address = inAddress.pointee
// This method returns whether or not the given property on the object can have its value changed.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
// log("Invoking: \(EQMDriver.getEQMObjectClassName(from: inObjectID)).isPropertySettable(\(propertyName(inAddress.pointee.mSelector)))")
let isSettable = DarwinBoolean(({
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
return obj.isPropertySettable(objectID: inObjectID, address: address)
} else {
return false
}
})())
// log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertySettable(\(propertyName(address.mSelector)), \(scopeName(address.mScope))) = \(isSettable)")
outIsSettable.pointee = isSettable
return noErr
}
func EQM_GetPropertyDataSize (
inDriver: AudioServerPlugInDriverRef,
inObjectID: AudioObjectID,
inClientProcessID: pid_t,
inAddress: UnsafePointer<AudioObjectPropertyAddress>,
inQualifierDataSize: UInt32,
inQualifierData: UnsafeRawPointer?,
outDataSize: UnsafeMutablePointer<UInt32>
) -> OSStatus {
let address = inAddress.pointee
// This method returns the byte size of the property's data.
guard EQMDriver.validateDriver(inDriver) && EQMDriver.validateObject(inObjectID) else { return kAudioHardwareBadObjectError }
let size = ({ () -> UInt32? in
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
return obj.getPropertyDataSize(objectID: inObjectID, address: address)
} else {
return nil
}
})()
log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertyDataSize(\(propertyName(address.mSelector)), \(scopeName(address.mScope))) = \(size ?? 0)")
if size != nil {
outDataSize.pointee = size!
return noErr
} else {
return kAudioHardwareUnknownPropertyError
}
}
func EQM_GetPropertyData (
inDriver: AudioServerPlugInDriverRef,
inObjectID: AudioObjectID,
inClientProcessID: pid_t,
inAddress: UnsafePointer<AudioObjectPropertyAddress>,
inQualifierDataSize: UInt32,
inQualifierData: UnsafeRawPointer?,
inDataSize: UInt32,
outDataSize: UnsafeMutablePointer<UInt32>,
outData: UnsafeMutableRawPointer
) -> OSStatus {
let address = inAddress.pointee
// Fetches the data of the given property and places it in the provided buffer.
guard EQMDriver.validateDriver(inDriver) && EQMDriver.validateObject(inObjectID) else {
// log("Invalid driver or object id while in EQM_GetPropertyData(\(propertyName(address.mSelector)), \(scopeName(address.mScope))")
return kAudioHardwareBadObjectError
}
log("Invoking: \(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertyData(\(propertyName(inAddress.pointee.mSelector))) - Size requested: \(inDataSize)")
let data = ({ () -> EQMObjectProperty? in
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
return obj.getPropertyData(objectID: inObjectID, address: address, inData: inQualifierData)
} else {
return nil
}
})()
log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertyData(\(propertyName(address.mSelector)), \(scopeName(address.mScope))) - Size requested: \(inDataSize) = \(String(describing: data))")
if data != nil {
let status = data!.write(to: outData, size: outDataSize, requestedSize: inDataSize)
return status
} else {
outDataSize.pointee = 0
return kAudioHardwareUnknownPropertyError
}
}
func EQM_SetPropertyData (
inDriver: AudioServerPlugInDriverRef,
inObjectID: AudioObjectID,
inClientProcessID: pid_t,
inAddress: UnsafePointer<AudioObjectPropertyAddress>,
inQualifierDataSize: UInt32,
inQualifierData: UnsafeRawPointer?,
inDataSize: UInt32,
inData: UnsafeRawPointer
) -> OSStatus {
let address = inAddress.pointee
// Tells an object to change the value of the given property.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
log("PID(\(inClientProcessID)) \(EQMDriver.getEQMObjectClassName(from: inObjectID)).setPropertyData(\(propertyName(address.mSelector)), \(scopeName(address.mScope))) = \(String(describing: inData))")
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
var changedProperties: [AudioObjectPropertyAddress] = []
let status = obj.setPropertyData(
client: EQMClients.get(processId: inClientProcessID),
objectID: inObjectID,
address: address,
data: inData,
changedProperties: &changedProperties
)
EQMDriver.propertiesUpdated(objectId: inObjectID, changedProperties: changedProperties)
log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).setPropertyData(\(propertyName(address.mSelector)), \(scopeName(address.mScope))) - Status: \(status) - Changed Properties: \(changedProperties)")
return status
} else {
return kAudioHardwareBadObjectError
}
}
// MARK: - IO Operations
func EQM_StartIO (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32) -> OSStatus {
// This call tells the device that IO is starting for the given client. When this routine
// returns, the device's clock is running and it is ready to have data read/written. It is
// important to note that multiple clients can have IO running on the device at the same time.
// So, work only needs to be done when the first client starts. All subsequent starts simply
// increment the counter.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
EQMDriver.mutex.lock()
let status = EQMDevice.startIO()
EQMDriver.mutex.unlock()
return status
}
func EQM_StopIO (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32) -> OSStatus {
// This call tells the device that the client has stopped IO. The driver can stop the hardware
// once all clients have stopped.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
EQMDriver.mutex.lock()
let status = EQMDevice.stopIO()
EQMDriver.mutex.unlock()
return status
}
func EQM_GetZeroTimeStamp (
inDriver: AudioServerPlugInDriverRef,
inDeviceObjectID: AudioObjectID,
inClientID: UInt32,
outSampleTime: UnsafeMutablePointer<Float64>,
outHostTime: UnsafeMutablePointer<UInt64>,
outSeed: UnsafeMutablePointer<UInt64>
) -> OSStatus {
// This method returns the current zero time stamp for the device. The HAL models the timing of
// a device as a series of time stamps that relate the sample time to a host time. The zero
// time stamps are spaced such that the sample times are the value of
// kAudioDevicePropertyZeroTimeStampPeriod apart. This is often modeled using a ring buffer
// where the zero time stamp is updated when wrapping around the ring buffer.
//
// For this device, the zero time stamps' sample time increments every kDevice_RingBufferSize
// frames and the host time increments by kDevice_RingBufferSize * gDevice_HostTicksPerFrame.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
let status = EQMDevice.getZeroTimeStamp(
outSampleTime: outSampleTime,
outHostTime: outHostTime,
outSeed: outSeed
)
return status
}
func EQM_WillDoIOOperation (
inDriver: AudioServerPlugInDriverRef,
inDeviceObjectID: AudioObjectID,
inClientID: UInt32,
inOperationID: UInt32,
outWillDo: UnsafeMutablePointer<DarwinBoolean>,
outWillDoInPlace: UnsafeMutablePointer<DarwinBoolean>
) -> OSStatus {
// Asks the plug-in whether or not the device will perform the given phase of the IO cycle for a particular device.
// This method returns whether or not the device will do a given IO operation. For this device,
// we only support reading input data and writing output data.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
var willDo = false
var willDoInPlace = true
switch inOperationID {
case AudioServerPlugInIOOperation.writeMix.rawValue:
willDo = true
willDoInPlace = true
break
case AudioServerPlugInIOOperation.readInput.rawValue:
willDo = true
willDoInPlace = true
break
default: break
}
outWillDo.pointee = DarwinBoolean(willDo)
outWillDoInPlace.pointee = DarwinBoolean(willDoInPlace)
return noErr
}
func EQM_BeginIOOperation (
inDriver: AudioServerPlugInDriverRef,
inDeviceObjectID: AudioObjectID,
inClientID: UInt32,
inOperationID: UInt32,
inIOBufferFrameSize: UInt32,
inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>
) -> OSStatus {
// Tells the plug-in that the Host is about to begin a phase of the IO cycle for a particular device.
// This is called at the beginning of an IO operation. This device doesn't do anything, so just
// check the arguments and return.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
return noErr
}
func EQM_DoIOOperation (
inDriver: AudioServerPlugInDriverRef,
inDeviceObjectID: AudioObjectID,
inStreamObjectID: AudioObjectID,
inClientID: UInt32,
inOperationID: UInt32,
inIOBufferFrameSize: UInt32,
inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>,
ioMainBuffer: UnsafeMutableRawPointer?,
ioSecondaryBuffer: UnsafeMutableRawPointer?
) -> OSStatus {
// This is called to actuall perform a given operation.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
guard let sample = ioMainBuffer?.assumingMemoryBound(to: Float32.self) else {
return noErr
}
let client = EQMClients.get(clientId: inClientID)
let status = EQMDevice.doIO(
client: client,
operationID: inOperationID,
sample: sample,
cycleInfo: inIOCycleInfo.pointee,
frameSize: inIOBufferFrameSize
)
return status
}
func EQM_EndIOOperation (
inDriver: AudioServerPlugInDriverRef,
inDeviceObjectID: AudioObjectID,
inClientID: UInt32,
inOperationID: UInt32,
inIOBufferFrameSize: UInt32,
inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>
) -> OSStatus {
// This is called at the end of an IO operation. This device doesn't do anything, so just check
// the arguments and return.
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
return noErr
}