Creating an Audio Server Driver Plug-in
At a glance
| Item | Summary |
|---|---|
| Purpose | Build a virtual audio device by creating a custom driver plug-in. |
| App architecture | A procedural C plug-in where NullAudio_Create returns a singleton driver reference backed by a static Core Audio callback table and file-scoped state. |
| Main patterns | CFPlugIn factory, callback-table interface, mutex-guarded module state |
| Project style | One C translation unit contains the exported factory, static callbacks, and state for one fixed virtual audio device. |
Project structure
Source bundle/
├── NullAudio.c
├── Configuration/
│ └── SampleCode.xcconfig
├── NullAudio-Info.plist
└── NullAudio.xcodeproj/
├── .xcodesamplecode.plist
└── project.pbxproj
Structure observations
NullAudio.cis both the plug-in implementation and the state boundary; there is no app-defined class hierarchy.NullAudio-Info.plistidentifies the bundle as a loadable plug-in, while the C factory exposes its driver interface.- The sample deliberately models one fixed device, so its plug-in, box, device, stream, and control state stays at file scope.
Overall architecture
flowchart LR
Host["Core Audio HAL"]
Factory["NullAudio_Create factory"]
DriverRef["Static AudioServerPlugInDriverRef"]
Callbacks["NullAudio_* callback table"]
State["File-scope plug-in and device state"]
Host --> Factory
Factory --> DriverRef
DriverRef --> Callbacks
Callbacks --> State
Callbacks --> Host
Reference code
NullAudio.c:234 — static driver interface and reference
static AudioServerPlugInDriverInterface gAudioServerPlugInDriverInterface = {
NULL,
NullAudio_QueryInterface,
NullAudio_AddRef,
NullAudio_Release,
NullAudio_Initialize,
// ... property and I/O callbacks ...
};
static AudioServerPlugInDriverInterface* gAudioServerPlugInDriverInterfacePtr =
&gAudioServerPlugInDriverInterface;
static AudioServerPlugInDriverRef gAudioServerPlugInDriverRef =
&gAudioServerPlugInDriverInterfacePtr;NullAudio.c:265 — CFPlugIn factory
void* NullAudio_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID)
{
void* theAnswer = NULL;
if (CFEqual(inRequestedTypeUUID, kAudioServerPlugInTypeUUID)) {
theAnswer = gAudioServerPlugInDriverRef;
}
return theAnswer;
}Interpretation
The Core Audio host discovers the exported factory, receives the sample’s singleton driver reference, and invokes the functions stored in gAudioServerPlugInDriverInterface. Those static callbacks read or mutate the translation unit’s shared plug-in and device state.
Ownership and state
flowchart TD
Module["NullAudio.c translation unit"]
Interface["Static driver interface and reference"]
PlugInState["gPlugIn_* and gBox_* state"]
DeviceState["gDevice_*, gStream_*, and control state"]
Locks["State and I/O mutexes"]
Module --> Interface
Module --> PlugInState
Module --> DeviceState
Locks -. "coordinate access" .-> PlugInState
Locks -. "coordinate access" .-> DeviceState
Ownership evidence
NullAudio.c:126 — explicit file-scope state boundary
// This driver only ever has a single device. If multiple devices were supported,
// this state would need to be encapsulated in one or more structs.
static pthread_mutex_t gPlugIn_StateMutex = PTHREAD_MUTEX_INITIALIZER;
static UInt32 gPlugIn_RefCount = 0;
static AudioServerPlugInHostRef gPlugIn_Host = NULL;
static CFStringRef gBox_Name = NULL;
static Boolean gBox_Acquired = true;
static pthread_mutex_t gDevice_IOMutex = PTHREAD_MUTEX_INITIALIZER;
static Float64 gDevice_SampleRate = 44100.0;
static UInt64 gDevice_IOIsRunning = 0;
static Float64 gDevice_HostTicksPerFrame = 0.0;| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
NullAudio.c file scope |
gAudioServerPlugInDriverInterface and gAudioServerPlugInDriverRef |
Statically initialized singleton driver contract | NullAudio_Create exposes the reference; the backing declarations remain translation-unit local. |
NullAudio.c file scope |
gPlugIn_* and gBox_* state |
Stores the one plug-in and box instance’s state | Static NullAudio_* callbacks mutate it, with shared accesses coordinated by gPlugIn_StateMutex. |
NullAudio.c file scope |
gDevice_*, gStream_*, and control state |
Stores the fixed virtual device and its subobjects | Configuration, property, and I/O callbacks own mutation; state and I/O mutexes coordinate concurrent access. |
NullAudio_Initialize |
AudioServerPlugInHostRef (gPlugIn_Host) |
Stores the host callback interface for later notifications and persistence | Assigned during driver initialization and subsequently used by static callbacks. |
This is module ownership rather than object ownership: static storage lives for the loaded plug-in’s lifetime, and the source explicitly notes that a multi-device driver would need per-device structs instead of these globals.
Class and protocol design
NullAudio.c:177 — exported factory and static callback declarations
void* NullAudio_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID);
static HRESULT NullAudio_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* outInterface);
static ULONG NullAudio_AddRef(void* inDriver);
static ULONG NullAudio_Release(void* inDriver);
static OSStatus NullAudio_Initialize(AudioServerPlugInDriverRef inDriver,
AudioServerPlugInHostRef inHost);| Boundary | Responsibility | Depends on |
|---|---|---|
NullAudio_Create |
Externally linked CFPlugIn factory that returns the singleton driver reference for the supported plug-in type | Core Foundation plug-in discovery and kAudioServerPlugInTypeUUID |
gAudioServerPlugInDriverInterface |
Static function table implementing the Core Audio driver contract | AudioServerPlugInDriverInterface and the NullAudio_* callbacks |
NullAudio_* callbacks |
Handle reference counting, initialization, properties, configuration changes, timestamps, and audio I/O | Core Audio object IDs, host callbacks, and I/O cycle structures |
NullAudio.c file-scope state |
Represents the one fixed plug-in, box, device, streams, and controls | Static globals plus pthread mutexes |
There is no app-defined class or protocol. The procedural equivalent is the framework-defined callback-table contract plus a translation-unit-owned singleton state store.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
NullAudio_Create (NullAudio.c:265) |
external C linkage |
The non-static factory can be referenced outside NullAudio.c by the plug-in loader. |
This is the intentionally exposed construction boundary. |
gAudioServerPlugInDriverInterface (NullAudio.c:234) |
static |
The callback table is visible only within the translation unit. | Keep the contract implementation private behind the returned driver reference. |
gPlugIn_StateMutex (NullAudio.c:132) |
static |
The mutex and associated globals are visible only within NullAudio.c. |
Prevent other translation units from bypassing callback-controlled state access. |
NullAudio_Initialize (NullAudio.c:380) |
static |
The callback has internal linkage and is reached through the driver interface table. | Expose behavior through the Core Audio contract rather than as a separate C API. |
Reference code
NullAudio.c:177 — linkage boundary
void* NullAudio_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID);
static HRESULT NullAudio_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* outInterface);
static OSStatus NullAudio_Initialize(AudioServerPlugInDriverRef inDriver,
AudioServerPlugInHostRef inHost);In C, static at file scope provides translation-unit privacy. The factory is deliberately non-static; the callback implementations and mutable singleton state remain internal.
Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Plug-in discovery and interface handoff | NullAudio_Create and gAudioServerPlugInDriverRef |
The exported factory is the only public construction boundary. |
| Driver lifecycle and host binding | NullAudio_Initialize |
Initialization stores the host reference and restores persisted box state before the HAL scans published objects. |
| Property routing | NullAudio_HasProperty, NullAudio_GetPropertyData, NullAudio_SetPropertyData and object-specific helpers |
Generic callbacks dispatch by fixed object ID to plug-in, box, device, stream, or control handlers. |
| Device timing and audio I/O | NullAudio_StartIO, NullAudio_GetZeroTimeStamp, NullAudio_DoIOOperation |
The Core Audio callback table owns the real-time-facing device path. |
| Shared state | NullAudio.c file scope |
The source intentionally uses globals because the sample publishes exactly one fixed device. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| CFPlugIn factory | NullAudio.c:265 |
A small externally linked factory validates the requested type and returns the singleton driver interface. |
| Callback-table interface | NullAudio.c:234 |
Core Audio calls a framework-defined table of procedural lifecycle, property, configuration, timestamp, and I/O functions. |
| Mutex-guarded module state | NullAudio.c:126 |
File-scoped state is compact for one device, but the source notes that multiple devices would require encapsulated per-object structs. |
Naming conventions
- Functions use the
NullAudio_prefix to namespace the factory and every Core Audio callback in C. - File-scoped mutable state uses domain prefixes such as
gPlugIn_,gBox_,gDevice_, andgStream_. - Constants use
k...names, including fixed object identifiers such askObjectID_DeviceandkObjectID_Stream_Input. - The single
NullAudio.cfile emphasizes the procedural contract; it is not organized as one type per file.
Architecture takeaways
NullAudio_Createis the source-visible plug-in entry; it returns a statically initializedAudioServerPlugInDriverRefrather than constructing an object graph.gAudioServerPlugInDriverInterfaceis the architectural contract: Core Audio reaches all lifecycle, property, timing, and I/O behavior through its callback slots.NullAudio.cowns singleton state because the sample has one fixed device; the source explicitly recommends structs for a multi-device design.- External linkage is limited to the factory, while callbacks and state are
staticimplementation details. - The design is procedural and callback-driven, not protocol-oriented in the Swift sense.
Source map
| Source file | Relevant symbols |
|---|---|
NullAudio.c |
NullAudio_Create, gAudioServerPlugInDriverInterface, gAudioServerPlugInDriverRef, NullAudio_* callbacks, file-scope plug-in/device state |