Abstract base class for a single piece of audio hardware. The IOAudioDevice provides the central coordination point for an audio driver.
- macOS 10.1+
An audio driver is required to subclass IOAudioDevice in order to provide working audio to the system. A single driver instance will contain a single instance of the driver's IOAudioDevice subclass. The subclass is responsible for mapping all hardware device resources from the service provider nub. It must control access to the hardware so that the hardware doesn't get into an inconsistent state. It is possible that different threads may make requests of the hardware at the same time. The IOAudioDevice superclass provides an IOWorkLoop and IOCommandGate on the IOWorkLoop through which all hardware accesses may be synchronized. All entry points to all parts of the driver controlled by the IOAudioFamily will be synchronized through this one IOWorkLoop.
The IOAudioDevice subclass is responsible for creating the rest of the pieces of the driver. It must identify and create all IOAudioEngines that are not automatically created by the system (i.e. those that are not matched and instantiated by IOKit directly).
The IOAudioDevice subclass must enumerate and create all IOAudioControls to match the device capabilities.
It must also execute control value chages when requested by the system (i.e. volume adjustments).
In order to allow sleep and wake to work on the system, the IOAudioDevice subclass is responsible for performing the necessary actions to sleep and wake its hardware (and restore necessary state on wake).
The IOAudioDevice class provides timer services that allow different elements in the audio driver to receive timer notifications as needed. These services were designed with the idea that most timed events in a typical audio driver need to be done at least as often as a certain interval. Further, it is designed with the idea that being called more often than the specified interval doesn't hurt anything - and in fact may help. With this in mind, the timer services provided by the IOAudioDevice class allow different targets to register for timer callbacks at least as often as the specified interval. The actual interval will be the smallest of the intervals of all of the callbacks. This way, we avoid the overhead of having many timers in a single audio device. As an example, each output IOAudioEngine has a timer to run the erase head. It doesn't hurt to have the erase head run more often. Also, a typical IOAudioDevice subclass may need to run a timer to check for device state changes (e.g. jack insertions).
There are a number of strings passed from the driver to the CoreAudio.framework and then into applications. All of those strings should be localized by the driver. In order to do that the kext bundle should have localized string files following the macOS localization instructions. The IOAudioDevice should contain a property with the name of the bundle/kext that contains the localized string resources. To do that, the driver's personality in the bundle resources could have a property named 'IOAudioDeviceLocalizedBundle' with the path of the bundle/kext relative to '/System/Library/Extensions'. It could also be set by the IOAudioDevice subclass in its initHardware() function. To do so, call setProperty(kIOAudioDeviceLocalizedBundleKey, "Driver.kext").
In a typical driver, the IOAudioDevice subclass will implement initHardware() to perform the hardware initialization and driver construction. Within that initialization it must create at least one IOAudioEngine instance and activate it. In order to activate a new IOAudioEngine activateAudioEngine() should be called for each one. It must create the IOAudioControls matching the hardware capabilities to allow the system to set volume, mute and input selection. To add those controls to the driver, each control should be attached to the IOAudioEngine to which it applies by calling addDefaultAudioControl() on the IOAudioEngine. During initialization it should also call setDeviceName(), setDeviceShortName() and setManufacturerName() with localized strings representing each of the attributes.
If the driver is to work properly after sleep/wake, it must implement performPowerStateChange() and deal with the sleep and wake transitions. It may also deal with the idle state transitions to turn off device power when it isn't in use (especially useful for devices attached to a portable running on battery power).