Sample CodemacOSReviewed 2026-07-21View on Apple Developer

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.c is both the plug-in implementation and the state boundary; there is no app-defined class hierarchy.
  • NullAudio-Info.plist identifies 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

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

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_, and gStream_.
  • Constants use k... names, including fixed object identifiers such as kObjectID_Device and kObjectID_Stream_Input.
  • The single NullAudio.c file emphasizes the procedural contract; it is not organized as one type per file.

Architecture takeaways

  • NullAudio_Create is the source-visible plug-in entry; it returns a statically initialized AudioServerPlugInDriverRef rather than constructing an object graph.
  • gAudioServerPlugInDriverInterface is the architectural contract: Core Audio reaches all lifecycle, property, timing, and I/O behavior through its callback slots.
  • NullAudio.c owns 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 static implementation 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