Реализация драйвера аудио
Как обсуждено в главе Проект семьи Аудио, пишущий драйвер аудио с помощью семьи Audio требует, в объектно-ориентированных условиях, чтобы Вы сделали некоторые определенные вещи в своем коде:
Создайте подкласс
IOAudioDevice
который, среди прочего, инициализирует аппаратные средства и регистры для уведомлений сна/следа.Создайте подкласс
IOAudioEngine
который, среди прочего, инициализирует механизм I/O и останавливает и запускает его.Создайте, сконфигурируйте и присоедините к
IOAudioEngine
возразите числуIOAudioStream
иIOAudioControl
объекты, надлежащие Вашему драйверу.Реагируйте на изменения значения в
IOAudioControl
объекты.В отдельном модуле кода (но как часть
IOAudioEngine
реализация подкласса), реализуйте водительское отсечение и преобразование подпрограмм.
Эта глава будет вести Вас через эти шаги реализации. Это использует в качестве источника кода проект SamplePCIAudioDriver в качестве примера (расположенный в /Developer/Examples/Kernel/IOKit/Audio/Templates
когда Вы устанавливаете пакет Разработчика). В интересах краткости эта глава не использует весь код, найденный в том проекте, и разделяет комментарии от кода. Обратитесь к проекту SamplePCIAudioDriver для полного спектра кода, и комментирует его.
Установка проекта
Даже перед созданием проекта для драйвера аудио необходимо рассмотреть некоторые элементные фасеты проекта. Исследуйте аудио аппаратные средства и решите, какие объекты аудио семьи требуются, чтобы поддерживать их. Конечно, Ваш драйвер должен иметь тот IOAudioDevice
объект (инстанцированный от пользовательского подкласса), но сколько IOAudioEngine
, IOAudioStream
, и IOAudioControl
объекты необходимо ли создать?
Таблица 3-1 обеспечивает матрицу решения для определения, сколько объектов аудио семьи каждого вида, в котором Вы нуждаетесь.
Вопрос |
Что создать |
---|---|
Есть ли демонстрационные буферы различных размеров? |
Создайте пользовательское |
Сколько I/O или механизмы DMA находятся там на устройстве? |
Создайте пользовательское |
Сколько отдельные или чередованные демонстрационные буферы там? |
Создайте |
Сколько управляемые атрибуты там (объем, усиление, бесшумный режим, и т.д.)? |
Создайте |
Проект SamplePCIAudioDriver требует одного пользовательского IOAudioEngine
разделите объект на подклассы, два IOAudioStream
объекты (ввод и вывод), и шесть IOAudioControl
объекты (левый и правый выходной объем, левое и правое входное усиление и бесшумный режим ввода и вывода).
Также необходимо решить, какие свойства драйверу, должно быть, придется соответствовать против куска провайдера и указать те свойства в водительском IOKitPersonalities
словарь. В индивидуальности SamplePCIAudioDriver (см. рисунок 3-1), провайдер является семьей PCI, и класс куска IOPCIDevice
. Кроме того, драйвер аудио PCI обычно указывал бы поставщика и устройство регистры ID (основной или подсистема) как значение IOPCIMatch
ключ. (Обратите внимание на то, что в примере SamplePCIAudioDriver, поставщике и устройстве регистры ID указаны как нули; для Вашего драйвера Вы заменили бы надлежащими значениями.) Наконец, для Вашего IOClass
свойство, добавьте имя Вашего IOAudioDevice
разделите на подклассы к стандартной конструкции обратного DNS com_
компания_driver
_
; в случае проекта SamplePCIAudioDriver, IOClass
значение com_MyCompany_driver_SamplePCIAudioDevice
.
Конечно, если бы Ваш водительский провайдер отличается (скажите, USB или FireWire), соответствующие свойства, что Вы указали бы в IOKitPersonalities
словарь отличался бы.
Как рисунок 3-1 предполагает, также удостоверьтесь, что Вы указываете другие необходимые свойства в своем водительском Info.plist
файл, включая управление версиями и информацию о зависимостях в OSBundleLibraries
словарь.
Реализация подкласса IOAudioDevice
Каждый драйвер аудио Набора I/O должен реализовать подкласс IOAudioDevice
. Когда драйвер загружается, один экземпляр этого класса создается. IOAudioDevice
объект является центральным, координирующим объектом драйвера; это представляет аудио аппаратные средства в полном смысле.
Несмотря на его центральную роль, IOAudioDevice
подкласс обычно не делает столько же сколько IOAudioEngine
подкласс. Это просто инициализирует аппаратные средства при запуске и создает пользовательское IOAudioEngine
объекты требуются драйвером. Это может также создать IOAudioControl
объекты, используемые драйвером и, реагируют на запросы для изменения значений этих средств управления, но IOAudioEngine
подкласс мог сделать эти задачи вместо этого. В примере, используемом для этой главы (SamplePCIAudioDriver), IOAudioDevice
подкласс создает и управляет средствами управления устройства.
Начните путем добавления заголовочного файла и файла реализации для IOAudioDevice
суперкласс Вы собираетесь реализовать. В заголовочном файле указать IOAudioDevice
как суперкласс и обеспечивают необходимые объявления.
Перечисление 3-1 показывает начало SamplePCIAudioDevice.h
.
Перечисление 3-1 Частичное объявление класса подкласса IOAudioDevice
#include <IOKit/audio/IOAudioDevice.h> |
typedef struct SamplePCIAudioDeviceRegisters { |
UInt32 reg1; |
UInt32 reg2; |
UInt32 reg3; |
UInt32 reg4; |
} SamplePCIAudioDeviceRegisters; |
class IOPCIDevice; |
class IOMemoryMap; |
#define SamplePCIAudioDevice com_MyCompany_driver_SamplePCIAudioDevice |
class SamplePCIAudioDevice : public IOAudioDevice |
{ |
friend class SampleAudioEngine; |
OSDeclareDefaultStructors(SamplePCIAudioDevice) |
IOPCIDevice *pciDevice; |
IOMemoryMap *deviceMap; |
SamplePCIAudioDeviceRegisters *deviceRegisters; |
// ... |
}; |
Инициализация аппаратного обеспечения
Драйверы аудио Набора I/O не должны переопределять IOService
::
start
метод. Вместо этого значение по умолчанию IOAudioDevice
реализация start
сначала вызывает реализацию суперкласса и затем вызывает initHardware
метод подкласса. Ваш IOAudioDevice
подкласс должен переопределить initHardware
метод.
Ваша реализация initHardware
должен сделать две общих вещи:
Это должно выполнить любые необходимые специфичные для аппаратных средств инициализации (и на провайдере и на аудио сторонах), таких как отображающиеся ресурсы и установка аппаратных средств к известному состоянию. Это также включает создание и инициализацию необходимых объектов семьи Audio.
Это должно определить имена, которыми драйвер должен быть известен Аудио HAL и его клиенты.
Если initHardware
вызов успешно выполняется, IOAudioDevice
суперкласс (в start
метод), устанавливает управление питанием, если семья, как предполагается, управляет питанием и затем вызывает registerService
сделать IOAudioDevice
объект, видимый в Реестре I/O.
Перечисление 3-2 показывает, как класс SamplePCIAudioDevice реализует initHardware
метод.
Перечисление 3-2 Реализовывая initHardware метод
bool SamplePCIAudioDevice::initHardware(IOService *provider) |
{ |
bool result = false; |
IOLog("SamplePCIAudioDevice[%p]::initHardware(%p)\n", this, provider); |
if (!super::initHardware(provider)) { |
goto Done; |
} |
pciDevice = OSDynamicCast(IOPCIDevice, provider); |
if (!pciDevice) { |
goto Done; |
} |
deviceMap = pciDevice->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0); |
if (!deviceMap) { |
goto Done; |
} |
deviceRegisters = (SamplePCIAudioDeviceRegisters *)deviceMap->getVirtualAddress(); |
if (!deviceRegisters) { |
goto Done; |
} |
pciDevice->setMemoryEnable(true); |
setDeviceName("Sample PCI Audio Device"); |
setDeviceShortName("PCIAudio"); |
setManufacturerName("My Company"); |
#error Put your own hardware initialization code here...and in other routines!! |
if (!createAudioEngine()) { |
goto Done; |
} |
result = true; |
Done: |
if (!result) { |
if (deviceMap) { |
deviceMap->release(); |
deviceMap = NULL; |
} |
} |
return result; |
} |
Первая часть этого метода делает некоторые специфичные для провайдера инициализации. Реализация получает провайдера, IOPCIDevice
объект, и с ним, конфигурирует карту для базовых регистров пространства конфигурации PCI. С этой картой это получает виртуальный адрес для регистров. Тогда это включает доступ к памяти PCI путем вызова setMemoryEnable
.
Затем, реализация SamplePCIAudioDevice устанавливает полное имя и краткое название устройства, а также имени производителя, делая эту информацию доступной для Аудио HAL.
Последний значительный вызов в этой реализации является вызовом к createAudioEngine
. Этот метод создает водительское IOAudioEngine
и IOAudioControl
объекты (и, косвенно, водительское IOAudioStream
объекты).
Создание объектов IOAudioEngine
В initHardware
метод, создайте экземпляр своего водительского IOAudioEngine
подкласс для каждого механизма I/O на устройстве. После того, как это будет создано и инициализировано, вызвать activateAudioEngine
сигнализировать к Аудио HAL, что механизм готов начать продавать аудио службы.
Подкласс SamplePCIAudioDevice создает собственный IOAudioEngine
объект в подпрограмме initHardware
именованный createAudioEngine
(см. Перечисление 3-3).
Перечисление 3-3 , Создающее объект IOAudioEngine
bool SamplePCIAudioDevice::createAudioEngine() |
{ |
bool result = false; |
SamplePCIAudioEngine *audioEngine = NULL; |
IOAudioControl *control; |
audioEngine = new SamplePCIAudioEngine; |
if (!audioEngine) { |
goto Done; |
} |
if (!audioEngine->init(deviceRegisters)) { |
goto Done; |
} |
// example code skipped... |
// Here create the driver’s IOAudioControl objects |
// (see next section)... |
activateAudioEngine(audioEngine); |
audioEngine->release(); |
result = true; |
Done: |
if (!result && (audioEngine != NULL)) { |
audioEngine->release(); |
} |
return result; |
} |
В этом примере, IOAudioDevice
подкласс создает необработанный экземпляр водительского подкласса IOAudioEngine
(SamplePCIAudioEngine) и затем инициализирует его, передающий в регистрах устройства, таким образом, объект может получить доступ к тем регистрам. Можно определить Ваш init
метод для взятия любого числа параметров.
Затем, IOAudioDevice
реализация активирует аудио механизм (activateAudioEngine
); это вызывает недавно создаваемый IOAudioEngine
объект start
и initHardware
методы, которые будут вызваны. Когда activateAudioEngine
возвраты, IOAudioEngine
готово начать продавать аудио службы к системе. Поскольку IOAudioDevice
суперкласс сохраняет водительское IOAudioEngine
объекты, убедиться выпустить каждого IOAudioEngine
возразите так, чтобы это было освобождено, когда завершается драйвер.
Создание и добавление объектов IOAudioControl
Типичный драйвер аудио Набора I/O должен инстанцировать нескольких IOAudioControl
объекты помочь ему управлять управляемыми атрибутами аудио аппаратных средств. Эти атрибуты включают такие вещи как объем, бесшумный режим и выбор ввода/вывода. Можно создать и управлять этими объектами управления в Вашем IOAudioEngine
подкласс или в Вашем IOAudioDevice
подкласс; это не имеет значения который.
Как получено в итоге в Таблице 3-2, семья Audio обеспечивает три подкласса IOAudioControl
то поведение реализации, определенное для трех функциональных типов управления. Инстанцируйте управления от подкласса, который является надлежащим управляемому атрибуту устройства.
Подкласс |
Цель |
---|---|
Для средств управления, таких как объем, где диапазон измеримых значений (таких как децибелы) связан с целочисленным диапазоном. |
|
Для средств управления, таких как бесшумный режим, где состояние или выключено или включено. |
|
|
Для средств управления, выбирающих дискретный атрибут, такой как входное усиление. |
Каждый подкласс (или тип управления) имеет a create
метод и удобный метод, определенный для подтипа управления. IOAudioTypes.h
заголовочный файл, определяющий константы для типа управления и подтипа, также определяет другие константы, предназначенные, чтобы быть предоставленным как параметры в методах создания управления. Таблица 3-3 суммирует категории, в которые падают эти константы.
Категория |
Цель |
Примеры и комментарии |
---|---|---|
Ввести |
Общая функция управления |
Уровень, переключаются, или селектор (каждое соответствие |
Подтип |
Цель управления |
Объем, бесшумный режим или ввод/вывод; удобные методы подкласса принимают подтип. |
Идентификатор канала |
Общие значения по умолчанию для каналов |
Правильный канал по умолчанию, значение по умолчанию центрирует канал, значение по умолчанию sub низкочастотный динамик, все каналы. |
Использование |
Как должно использоваться управление |
Вывод, ввод или передача. |
Посмотрите IOAudioTypes.h
для полного набора констант регулировки звука.
После создания IOAudioControl
объект необходимо сделать два дальнейших шага:
Установите обработчик изменения значения для управления.
Обработчик изменения значения является подпрограммой обратного вызова, вызывающейся, когда клиент Аудио HAL запрашивает изменение в управляемом атрибуте. Посмотрите Обработчики Изменения значения Управления Реализацией для больше на этих подпрограммах.
Добавьте
IOAudioControl
кIOAudioEngine
объект они связаны с.
В примере SamplePCIAudioDriver, IOAudioDevice
подкласс создает и инициализирует водительское IOAudioControl
объекты. Это происходит в createAudioEngine
метод; Перечисление 3-4 показывает создание и инициализацию одного управления.
Перечисление 3-4 , Создающее IOAudioControl, возражает и добавляющий его к объекту IOAudioEngine
// ... from createAudioEngine() |
control = IOAudioLevelControl::createVolumeControl( |
65535, // initial value |
0, // min value |
65535, // max value |
(-22 << 16) + (32768), // -22.5 in IOFixed (16.16) |
0, // max 0.0 in IOFixed |
kIOAudioControlChannelIDDefaultLeft, |
kIOAudioControlChannelNameLeft, |
0, // control ID - driver-defined |
kIOAudioControlUsageOutput); |
if (!control) { |
goto Done; |
} |
control->setValueChangeHandler((IOAudioControl::IntValueChangeHandler) |
volumeChangeHandler, this ); |
audioEngine->addDefaultAudioControl(control); |
control->release(); |
/* Here create more IOAudioControl objects for right output channel, |
** output mute,left and right input gain, and input mute. For each, set |
** value change handler and add to the IOAudioEngine |
*/ |
// ... |
В этом примере, IOAudioDevice
подкласс создает левый выходной регулятор громкости с целочисленным диапазоном от 0 до 65 535 и соответствующим диапазоном децибела от –22.5 до 0,0. Канал должен всегда связываться с IOAudioControl
объект. Вы делаете это при создании объекта путем указания констант (определенный в IOAudioDefines.h
) и для идентификатора канала и для названия канала. Необходимо также указать «использование», постоянное, который указывает как IOAudioControl
будет использоваться (ввод, выводиться, или передача).
Как только Вы добавили IOAudioControl
к IOAudioEngine
, необходимо выпустить его так, чтобы это было должным образом освобождено когда IOAudioEngine
объект сделан с ним.
Обработка Уведомлений Сна/Следа
Как контроллер мощности для Вашего устройства, необходимо зарегистрироваться для уведомлений сна/следа. Как минимум Ваши обработчики должны остановить и перезапустить любые аудио механизмы под своим управлением. В зависимости от устройства это может не быть достаточно, как бы то ни было.
В целом — и особенно для устройств PCI — питание устройства будет циклически повторено во время сна, но устройство не исчезнет из дерева устройств. Это означает, что Ваш драйвер не будет разъединен и повторно инициализирован. Таким образом, для этих устройств, крайне важно, чтобы Вы зарегистрировались для уведомлений сна/следа и повторно инициализировали свои регистры устройства к известному состоянию на следе. Иначе, неожиданное поведение может закончиться.
Для получения информации о том, как зарегистрироваться для уведомлений сна/следа, см. главу Управления питанием Основных принципов IOKit.
Реализация обработчиков изменения значения управления
Для каждого IOAudioControl
возразите, что Ваш драйвер создает, он должен реализовать то, что известно как обработчик изменения значения для него. (Это не подразумевает необходимость в Вас, должен создать отдельный обработчик для каждого управления; один обработчик мог использоваться для управления многократными средствами управления подобного типа.) Обработчик изменения значения является подпрограммой обратного вызова, вызывающейся, когда управляемый атрибут устройств связался с IOAudioControl
возразите должен быть изменен.
Заголовочный файл IOAudioControl.h
определяет три прототипа для обработчиков изменения значения управления:
typedef IOReturn (*IntValueChangeHandler)(OSObject *target, |
IOAudioControl *audioControl, SInt32 oldValue, SInt32 newValue); |
typedef IOReturn (*DataValueChangeHandler)(OSObject *target, |
IOAudioControl *audioControl, const void *oldData, UInt32 |
oldDataSize, const void *newData, UInt32 newDataSize); |
typedef IOReturn (*ObjectValueChangeHandler)(OSObject *target, |
IOAudioControl *audioControl, OSObject *oldValue, |
OSObject *newValue); |
Каждый прототип предназначается для различного вида значения управления: целое число, указатель на необработанные данные (void *
), и (libkern
) объект. Для большинства случаев целочисленный обработчик должен быть достаточным. Все существующие IOAudioControl
подклассы передают целочисленные значения IntValueChangeHandler
объект.
Существенная задача обработчика изменения значения состоит в том, чтобы обновить надлежащий атрибут аудио аппаратных средств к новому значению управления. Перечисление 3-5 показывает, как можно было бы реализовать обработчик изменения значения (исключая фактический устанавливающий атрибут код).
Перечисление 3-5 Реализовывая обработчик изменения значения управления
IOReturn SamplePCIAudioDevice::volumeChangeHandler(IOService *target, |
IOAudioControl *volumeControl, SInt32 oldValue, SInt32 newValue) |
{ |
IOReturn result = kIOReturnBadArgument; |
SamplePCIAudioDevice *audioDevice; |
audioDevice = (SamplePCIAudioDevice *)target; |
if (audioDevice) { |
result = audioDevice->volumeChanged(volumeControl, oldValue, |
newValue); |
} |
return result; |
} |
IOReturn SamplePCIAudioDevice::volumeChanged(IOAudioControl *volumeControl, |
SInt32 oldValue, SInt32 newValue) |
{ |
IOLog("SamplePCIAudioDevice[%p]::volumeChanged(%p, %ld, %ld)\n", this, |
volumeControl, oldValue, newValue); |
if (volumeControl) { |
IOLog("\t-> Channel %ld\n", volumeControl->getChannelID()); |
} |
// Add hardware volume code change |
return kIOReturnSuccess; |
} |
Причина вложенной реализации в этом примере состоит в том, что сам обратный вызов изменения значения должен быть прямой функцией языка C (в этом случае, это - статическая функция членства). Статическая функция просто передает сообщение к фактической цели для обработки.
Реализация подкласса IOAudioEngine
В дополнение к реализации подкласса IOAudioDevice
, писатели драйверов аудио должны также реализовать подкласс IOAudioEngine
. Этот подкласс должен определить атрибуты и поведение драйвера, которые являются определенными для механизма I/O аппаратных средств. Они включают указание размера и характеристик выборки и буферов соединения, получение текущего демонстрационного кадра по требованию, обработка прерываний для взятия метки времени, обработка изменений формата, и запуск и остановка механизма I/O по запросу.
Запустите путем определения интерфейса Вашего IOAudioEngine
подкласс в заголовочном файле. Перечисление 3-6 показывает основное содержание SamplePCIAudioEngine.h
файл.
Определение Интерфейса перечисления 3-6 класса SamplePCIAudioEngine
class SamplePCIAudioEngine : public IOAudioEngine |
{ |
OSDeclareDefaultStructors(SamplePCIAudioEngine) |
SamplePCIAudioDeviceRegisters *deviceRegisters; |
SInt16 *outputBuffer; |
SInt16 *inputBuffer; |
IOFilterInterruptEventSource *interruptEventSource; |
public: |
virtual bool init(SamplePCIAudioDeviceRegisters *regs); |
virtual void free(); |
virtual bool initHardware(IOService *provider); |
virtual void stop(IOService *provider); |
virtual IOAudioStream *createNewAudioStream(IOAudioStreamDirection |
direction, void *sampleBuffer, UInt32 sampleBufferSize); |
virtual IOReturn performAudioEngineStart(); |
virtual IOReturn performAudioEngineStop(); |
virtual UInt32 getCurrentSampleFrame(); |
virtual IOReturn performFormatChange(IOAudioStream *audioStream, |
const IOAudioStreamFormat *newFormat, const IOAudioSampleRate |
*newSampleRate); |
virtual IOReturn clipOutputSamples(const void *mixBuf, void *sampleBuf, |
UInt32 firstSampleFrame, UInt32 numSampleFrames, const |
IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream); |
virtual IOReturn convertInputSamples(const void *sampleBuf, void *destBuf, |
UInt32 firstSampleFrame, UInt32 numSampleFrames, const |
IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream); |
static void interruptHandler(OSObject *owner, IOInterruptEventSource |
*source, int count); |
static bool interruptFilter(OSObject *owner, IOFilterInterruptEventSource |
*source); |
virtual void filterInterrupt(int index); |
}; |
Большинство методов и типов, объявленных здесь, объяснены в следующих разделах — включая (например), почему существует кластер связанных с прерыванием методов.
Инициализация аппаратного обеспечения
Поскольку Вы выполнили в Вашем IOAudioDevice
подкласс, необходимо реализовать initHardware
метод в Вашем IOAudioEngine
подкласс для выполнения определенных инициализаций аппаратного обеспечения. IOAudioEngine
initHardware
метод вызывается косвенно когда IOAudioDevice
вызовы объектов activateAudioEngine
на IOAudioEngine
объект.
В Вашей реализации initHardware
, необходимо выполнить две общих задачи: сконфигурируйте механизм I/O и создайте IOAudioStream
объекты используются механизмом. Как часть инициализации, необходимо также реализовать init
метод, если что-либо специальное должно произойти до вызова initHardware
; в случае класса SamplePCIAudioEngine, init
вызовы метода реализация суперкласса и затем присваивают переданный - в регистрах устройства к переменной экземпляра.
Конфигурирование Механизма I/O
Конфигурирование механизма I/O аудио аппаратных средств включает завершение многих рекомендуемых задач:
Определите текущую частоту дискретизации и установите начальное использование частоты дискретизации
setSampleRate
.Вызвать
setNumSampleFramesPerBuffer
указать число демонстрационных кадров в каждом буфере, обслуживаемом этим механизмом I/O.Вызвать
setDescription
сделать имя механизма I/O доступным для Аудио клиентами HAL.Вызвать
setOutputSampleLatency
илиsetInputSampleLatency
(или оба метода, если надлежащий) для указания, сколько задержки существует на потоках ввода и вывода. Семья Audio делает эту информацию доступной для Аудио HAL, таким образом, это может передать его своим клиентам в целях синхронизации.Вызвать
setSampleOffset
удостоверяться, что Аудио HAL остается, по крайней мере, конкретное количество выборок далеко от верхней части механизма I/O. Эта установка полезна для устройств поблочной передачи.Создайте
IOAudioStream
объекты, которые будут использоваться механизмом I/O, и добавляют их кIOAudioEngine
. Посмотрите Создание Объекты IOAudioStream для подробных данных.Добавьте обработчик к своему логическому элементу команды для прерывания, запущенного механизмом I/O, когда это перенесется к началу демонстрационного буфера. (Это принимает «традиционное» прерывание.)
Выполните любые необходимые специфичные для механизма инициализации.
Перечисление 3-7 иллюстрирует, как класс SamplePCIAudioEngine выполняет некоторые из этих шагов. Обратите внимание на то, что некоторые начальные значения, такой как INITIAL_SAMPLE_RATE
, были определены более раннее использование #define
команды препроцессора.
Перечисление 3-7 , Конфигурирующее механизм I/O
bool SamplePCIAudioEngine::initHardware(IOService *provider) |
{ |
bool result = false; |
IOAudioSampleRate initialSampleRate; |
IOAudioStream *audioStream; |
IOWorkLoop *workLoop; |
if (!super::initHardware(provider)) { |
goto Done; |
} |
initialSampleRate.whole = INITIAL_SAMPLE_RATE; |
initialSampleRate.fraction = 0; |
setSampleRate(&initialSampleRate); |
setDescription("Sample PCI Audio Engine"); |
setNumSampleFramesPerBuffer(NUM_SAMPLE_FRAMES); |
workLoop = getWorkLoop(); |
if (!workLoop) { |
goto Done; |
} |
interruptEventSource = IOFilterInterruptEventSource::filterInterruptEventSource(this, |
OSMemberFunctionCast(IOInterruptEventAction, this, |
&SamplePCIAudioEngine::interruptHandler), |
OSMemberFunctionCast(Filter, this, |
&SamplePCIAudioEngine::interruptFilter), |
audioDevice->getProvider()); |
if (!interruptEventSource) { |
goto Done; |
} |
workLoop->addEventSource(interruptEventSource); |
outputBuffer = (SInt16 *)IOMalloc(BUFFER_SIZE); |
if (!outputBuffer) { |
goto Done; |
} |
inputBuffer = (SInt16 *)IOMalloc(BUFFER_SIZE); |
if (!inputBuffer) { |
goto Done; |
} |
audioStream = createNewAudioStream(kIOAudioStreamDirectionOutput, |
outputBuffer, BUFFER_SIZE); |
if (!audioStream) { |
goto Done; |
} |
addAudioStream(audioStream); |
audioStream->release(); |
audioStream = createNewAudioStream(kIOAudioStreamDirectionInput, |
inputBuffer, BUFFER_SIZE); |
if (!audioStream) { |
goto Done; |
} |
addAudioStream(audioStream); |
audioStream->release(); |
result = true; |
Done: |
return result; |
} |
Следующий раздел, Создавая Объекты IOAudioStream, описывает реализацию createNewAudioStream
, который это вызовы метода. Несколько других вещей в этом методе заслуживают немного больше обсуждения.
Во-первых, посреди метода несколько строк кода, создающих фильтр, прерывают источник события и добавляют его к циклу работы. Через этот источник события обработчик событий, указанный драйвером, получит прерывания, запущенные механизмом I/O. В случае SamplePCIAudioEngine драйвер хочет прерывание в основное время прерывания вместо вторичного времени прерывания из-за лучшей периодической точности. Чтобы сделать это, это создает IOFilterInterruptEventSource
объект, выполняющий вызов фильтрации к основному обработчику прерываний (interruptFilter
); обычная цель этого обратного вызова состоит в том, чтобы определить, какого вторичного обработчика прерываний нужно вызвать, если таковые имеются. SamplePCIAudioEngine в interruptFilter
подпрограмма (как Вы будете видеть во Взятии Метки времени) вызывает метод, фактически берущий метку времени и всегда возвращающийся false
указать, что нельзя вызвать вторичный обработчик. Для драйвера для получения прерываний должен быть включен источник события. Когда двигатель I/O запущен, это обычно делается.
Во-вторых, этот метод выделяет демонстрационные буферы ввода и вывода в подготовке к созданию IOAudioStream
объекты в двух вызовах к createNewAudioStream
. Метод выделения в этом примере является довольно элементарным и был бы более устойчивым в реальном драйвере. Также отметьте это BUFFER_SIZE
определяется ранее как:
NUM_SAMPLE_FRAMES * NUM_CHANNELS * BIT_DEPTH / 8 |
Другими словами, вычислите размер байта своих демонстрационных буферов путем умножения числа демонстрационных кадров в буфере числом каналов в аудиопотоке; тогда умножьте ту сумму на битовую глубину и разделите получающуюся сумму на 8 (диаметр долота одного байта).
Создание объектов IOAudioStream
Ваш IOAudioEngine
подкласс должен также создать IOAudioStream
объекты, когда это инициализирует механизм I/O (initHardware
). У Вас должен быть тот IOAudioStream
экземпляр для каждого демонстрационного буфера обслуживается механизмом I/O. В процессе создания объекта удостоверьтесь, что Вы делаете следующие вещи:
Инициализируйте его с
IOAudioEngine
возразите, что использует его (в этом случае, ВашIOAudioEngine
экземпляр подкласса).Инициализируйте поля a
IOAudioStreamFormat
структура со значениями, определенными для определенного формата.Вызвать
setSampleBuffer
для передачи фактической аппаратной выборки буферизуют к потоку. Если демонстрационный буфер находится в оперативной памяти, он должен быть выделен перед совершением этого вызова.SamplePCIAudioEngine
подкласс выделяет демонстрационные буферы (ввод и вывод) вinitHardware
прежде чем это вызоветcreateNewAudioStream
.Вызвать
addAvailableFormat
для каждого формата, в который может быть установлен поток. Как частьaddAvailableFormat
вызовите, укажите минимальные и максимальные частоты дискретизации для того формата.Как только Вы добавили все поддерживаемые форматы к
IOAudioStream
, вызватьsetFormat
указать начальный формат для аппаратных средств. В настоящее время,performFormatChange
вызывается в результатеsetFormat
вызвать.
Перечисление 3-8 показывает, как подкласс SamplePCIAudioEngine создает и инициализирует IOAudioStream
объект.
Создание перечисления 3-8 и инициализация объекта IOAudioStream
IOAudioStream *SamplePCIAudioEngine::createNewAudioStream(IOAudioStreamDirection |
direction, void *sampleBuffer, UInt32 sampleBufferSize) |
{ |
IOAudioStream *audioStream; |
audioStream = new IOAudioStream; |
if (audioStream) { |
if (!audioStream->initWithAudioEngine(this, direction, 1)) { |
audioStream->release(); |
} else { |
IOAudioSampleRate rate; |
IOAudioStreamFormat format = { |
2, // number of channels |
kIOAudioStreamSampleFormatLinearPCM, // sample format |
kIOAudioStreamNumericRepresentationSignedInt, |
BIT_DEPTH, // bit depth |
BIT_DEPTH, // bit width |
kIOAudioStreamAlignmentHighByte, // high byte aligned |
kIOAudioStreamByteOrderBigEndian, // big endian |
true, // format is mixable |
0 // driver-defined tag - unused by this driver |
}; |
audioStream->setSampleBuffer(sampleBuffer, sampleBufferSize); |
rate.fraction = 0; |
rate.whole = 44100; |
audioStream->addAvailableFormat(&format, &rate, &rate); |
rate.whole = 48000; |
audioStream->addAvailableFormat(&format, &rate, &rate); |
audioStream->setFormat(&format); |
} |
} |
return audioStream; |
} |
Запуск и остановка механизма I/O
Ваш IOAudioEngine
подкласс должен реализовать performAudioEngineStart
и performAudioEngineStop
запустить и остановить механизм I/O. Когда Вы запускаете двигатель, удостоверьтесь, что он запускается в начале демонстрационного буфера. Прежде, чем запустить двигатель I/O, Ваша реализация должна сделать две вещи:
Позвольте источнику события прерывания позволить механизму I/O запускать прерывания, поскольку это переносится от конца до начала демонстрационного буфера; в его обработчике прерываний,
IOAudioEngine
экземпляр может постоянно брать метки времени.Возьмите начальную метку времени для маркировки момента, который запустил аудио механизм, но сделайте так, не постепенно увеличивая количество цикла.
По умолчанию, метод takeTimeStamp
автоматически постепенно увеличивает текущее количество цикла, поскольку оно берет текущую метку времени. Но потому что Вы запускаете новое выполнение механизма I/O и не являетесь цикличным выполнением, Вы не хотите, чтобы было постепенно увеличено количество цикла. Указать это, передачу false
в takeTimeStamp
.
Перечисление 3-9 показывает, как класс SamplePCIAudioEngine реализует performAudioEngineStart
метод; фактический связанный с аппаратными средствами код, запускающий двигатель, не предоставляется.
Перечисление 3-9 , Запускающее двигатель I/O
IOReturn SamplePCIAudioEngine::performAudioEngineStart() |
{ |
IOLog("SamplePCIAudioEngine[%p]::performAudioEngineStart()\n", this); |
assert(interruptEventSource); |
interruptEventSource->enable(); |
takeTimeStamp(false); |
// Add audio - I/O start code here |
#error performAudioEngineStart() - add engine-start code here; driver will |
not work without it |
return kIOReturnSuccess; |
} |
В performAudioEngineStop
, обязательно отключите источник события прерывания перед остановкой механизма I/O.
Взятие метки времени
Главная ответственность Вашего IOAudioEngine
подкласс должен взять метку времени каждый раз циклы механизма I/O от конца демонстрационного буфера к началу демонстрационного буфера. Когда это цикличное выполнение происходит, Как правило, Вы программируете аппаратные средства для броска прерывания. Необходимо также установить обработчика прерываний, чтобы получить и обработать прерывание. В обработчике прерываний просто вызовите takeTimeStamp
без параметров; этот метод делает следующее:
Это получает ток (машина) время и устанавливает его как метку времени цикла в
IOAudioEngineStatus
- определенная область памяти совместно используется с Аудио клиентами.Это постепенно увеличивает количество цикла в том же
IOAudioEngineStatus
- определенная область общей памяти.
HAL Аудио требует обеих частей обновленной информации так, чтобы она могла отследить, где механизм I/O в настоящее время, и предскажите, где это будет в ближайшем будущем.
Подкласс SamplePCIAudioEngine использует IOFilterInterruptEventSource
объект в его механизме обработки прерываний. Как Инициализация аппаратного обеспечения описывает, когда подкласс создает этот объект, это указывает и подпрограмму фильтра прерывания и подпрограмму обработчика прерываний. Подпрограмму обработчика прерываний, однако, никогда не вызывают; вместо этого, подпрограмма фильтра прерывания вызывает другую подпрограмму непосредственно (filterInterrupt
), который вызывает takeTimeStamp
. Перечисление 3-10 показывает этот код.
Перечисление 3-10 SamplePCIAudioEngine прерывает фильтр и обработчик
bool SamplePCIAudioEngine::interruptFilter(OSObject *owner, |
IOFilterInterruptEventSource *source) |
{ |
SamplePCIAudioEngine *audioEngine = OSDynamicCast(SamplePCIAudioEngine, |
owner); |
if (audioEngine) { |
audioEngine->filterInterrupt(source->getIntIndex()); |
} |
return false; |
} |
void SamplePCIAudioEngine::filterInterrupt(int index) |
{ |
takeTimeStamp(); |
} |
Обратите внимание на то, что можно указать собственную метку времени вместо системы путем вызова takeTimeStamp
с AbsoluteTime
параметр (см. Технические Вопросы и ответы QA1398 и раздел «Using Kernel Time Abstractions» Руководства по программированию Ядра для получения информации о AbsoluteTime
). Эта альтернатива обычно не необходима, но может использоваться в случаях, где цикличное выполнение не обнаруживаемо до некоторого времени после фактического времени цикла. В этом случае, когда цикл произошел в прошлом, задержка может быть вычтена с текущего времени для указания.
Обеспечение позиции кадра воспроизведения
IOAudioEngine
подкласс должен реализовать getCurrentSampleFrame
возвратить текущий кадр аппаратных средств воспроизведения вызывающей стороне. Это значение (как Вы видите на рисунке 2-5) говорит вызывающую сторону, где воспроизведение происходит относительно запуска буфера.
Процесс стирающей головки использует это значение; это стирается (обнуляет), кадры в выборке и буферах соединения до, но не включая, демонстрационный кадр, возвращенный этим методом. Таким образом, несмотря на то, что демонстрационное встречное значение возвратилось, не должно быть точным, это никогда не должно быть больше, чем фактический демонстрационный счетчик. Если это больше, аудиоданные могут быть стерты стирающей головкой, прежде чем аппаратные средства будут иметь возможность играть его.
Реализация изменений формата и уровня
Если драйвер аудио поддерживает многократные форматы аудио или частоты дискретизации, он должен реализовать performFormatChange
метод для внесения этих изменений в аппаратных средствах, когда клиенты запрашивают их. Метод имеет параметры для нового формата и для новой частоты дискретизации; если любой из этих параметров NULL
, IOAudioEngine
подкласс должен изменить только элемент, который не является NULL
.
Несмотря на то, что драйвер SamplePCIAudioDriver имеет дело только с одним форматом аудио, это способно к двум частотам дискретизации, 44,1 килогерцам и 48 килогерцам. Перечисление 3-11 иллюстрирует как performFormatChange
реализован для изменения частоты дискретизации по запросу.
Перечисление 3-11 , Изменяющее частоту дискретизации
IOReturn SamplePCIAudioEngine::performFormatChange(IOAudioStream |
*audioStream, const IOAudioStreamFormat *newFormat, |
const IOAudioSampleRate *newSampleRate) |
{ |
IOLog("SamplePCIAudioEngine[%p]::peformFormatChange(%p, %p, %p)\n", this, |
audioStream, newFormat, newSampleRate); |
if (newSampleRate) { |
switch (newSampleRate->whole) { |
case 44100: |
IOLog("/t-> 44.1kHz selected\n"); |
// Add code to switch hardware to 44.1khz |
break; |
case 48000: |
IOLog("/t-> 48kHz selected\n"); |
// Add code to switch hardware to 48kHz |
break; |
default: |
IOLog("/t Internal Error - unknown sample rate selected.\n"); |
break; |
} |
} |
return kIOReturnSuccess; |
} |
Отсечение и преобразование выборок
Возможно, наиболее важная работа, которую делает драйвер аудиоустройства, преобразовывает аудиосэмплы между форматом, ожидаемым аппаратными средствами и форматом, ожидаемым клиентами аппаратных средств. В OS X формате по умолчанию аудиоданных в ядре, а также в Аудио HAL и все его клиенты являются 32-разрядной плавающей точкой. Однако аудио аппаратные средства обычно требуют, чтобы аудиоданные были в целочисленном формате.
Для выполнения этих преобразований драйвер аудио должен реализовать по крайней мере один из двух методов, в зависимости от направлений поддерживаемых аудиопотоков:
Реализация
clipOutputSamples
если Ваш драйвер имеет выводIOAudioStream
объект.Реализация
convertInputSamples
если Ваш драйвер имеет вводIOAudioStream
объект.
В дополнение к выполнению отсечения и преобразования, эти методы являются также хорошим местом для добавления специфичного для устройства кода фильтрации ввода и вывода. Например, определенная модель динамиков USB могла бы звучать лучше с небольшим высокочастотным спадом. (Обратите внимание на то, что, если это - единственная причина записи драйвера, необходимо обычно использовать AppleUSBAudio
плагин вместо этого, как описано в примере кода SampleUSBAudioPlugin.)
Поскольку эти методы выполняют код с плавающей точкой, Вы не можете включать их в тот же исходный файл как другой IOAudioEngine
методы Вы реализуете. Компилятор, по умолчанию, позволяет эмуляции с плавающей точкой препятствовать тому, чтобы были сгенерированы инструкции с плавающей точкой. Для обхождения этого создайте отдельную библиотеку, содержащую код с плавающей точкой, и скомпилируйте и соедините эту библиотеку в получающийся модуль ядра. Отдельная библиотека для проекта SamplePCIAudioDriver libAudioFloatLib
.
Частой ошибке, которую люди делают при разработке драйвера аудио, или не удается записать эти методы или не удающийся включать эту дополнительную библиотеку при соединении KEXT. Когда это произойдет, Вы выполнитесь clipOutputSamples
и convertInputSamples
методы, встроенные в базовый класс. Эти методы являются просто тупиками тот возврат kIOReturnUnsupported
(0xe00002c7
, или -536870201
). Если Вы видите эту ошибку, возвращенную одним из этих методов, необходимо удостовериться, что Вы соединяете свой KEXT правильно.
clipOutputSamples
метод передается шесть параметров:
Указатель на запуск источника (соединение) буфер
Указатель на запуск целевого (демонстрационного) буфера
Индекс первого демонстрационного кадра в буферах, который отсечет и преобразует
Число демонстрационных кадров, чтобы отсечь и преобразовать
Указатель на текущий формат (структура
IOAudioStreamFormat
) из аудиопотокаУказатель на
IOAudioStream
возразите, что этот метод продолжает работать
Ваша реализация должна сначала отсечь любые выборки с плавающей точкой в буфере соединения, выходящие за пределы диапазона –1.0 к 1,0 и затем преобразовывающие значение с плавающей точкой в сопоставимое значение в формате, ожидаемом аппаратными средствами. Тогда копия, которые оценивают соответствующим позициям в демонстрационном буфере. Перечисление 3-12 иллюстрирует, как SamplePCIAudioDriver реализует clipOutputSamples
метод.
Отсечение перечисления 3-12 и преобразование выходных выборок
IOReturn SamplePCIAudioEngine::clipOutputSamples(const void *mixBuf, |
void *sampleBuf, UInt32 firstSampleFrame, UInt32 numSampleFrames, |
const IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream) |
{ |
UInt32 sampleIndex, maxSampleIndex; |
float *floatMixBuf; |
SInt16 *outputBuf; |
floatMixBuf = (float *)mixBuf; |
outputBuf = (SInt16 *)sampleBuf; |
maxSampleIndex = (firstSampleFrame + numSampleFrames) * |
streamFormat->fNumChannels; |
for (sampleIndex = (firstSampleFrame * streamFormat->fNumChannels); |
sampleIndex < maxSampleIndex; sampleIndex++) { |
float inSample; |
inSample = floatMixBuf[sampleIndex]; |
const static float divisor = ( 1.0 / 32768 ); |
// Note: A softer clipping operation could be done here |
if (inSample > (1.0 - divisor)) { |
inSample = 1.0 - divisor; |
} else if (inSample < -1.0) { |
inSample = -1.0; |
} |
outputBuf[sampleIndex] = (SInt16) (inSample * 32768.0); |
} |
return kIOReturnSuccess; |
} |
Вот несколько комментариев к этому определенному примеру:
Это запускается путем кастинга
void *
буферы кfloat *
для буфера соединения иSInt16 *
для демонстрационного буфера; в то время как буфер соединения всегда, в этом проекте аппаратное использование подписало 16-разрядные целые числа для его выборокfloat *
.Затем, это вычисляет верхний предел демонстрационного индекса для предстоящего отсечения и преобразования цикла.
Циклы метода через соединение и выборку буферизуют и выполняют клип и операции преобразования на одной выборке за один раз.
Это выбирает выборку с плавающей точкой от буфера соединения и отсекает его (если необходимый) к диапазону между-1.0 и 1.0.
Это масштабирует и преобразовывает значение с плавающей точкой в надлежащую 16-разрядную целочисленную выборку со знаком и пишет его в соответствующее расположение в демонстрационном буфере.
Параметры передали в convertInputSamples
метод является почти тем же как теми для clipOutputSamples
метод. Единственная разница - то, что, вместо указателя на буфер соединения, передается указатель на целевой буфер с плавающей точкой; это - буфер, который использует Аудио HAL. В Вашей водительской реализации этого метода сделайте противоположность clipOutputSamples
: преобразуйте от аппаратного формата до системы 32-разрядный формат с плавающей точкой. Никакое отсечение не необходимо, потому что Ваш процесс преобразования может управлять границами значений с плавающей точкой.
Перечисление 3-13 показывает, как проект SamplePCIAudioDriver реализует этот метод.
Перечисление 3-13 , Преобразовывающее входные выборки.
IOReturn SamplePCIAudioEngine::convertInputSamples(const void *sampleBuf, |
void *destBuf, UInt32 firstSampleFrame, UInt32 numSampleFrames, |
const IOAudioStreamFormat *streamFormat, IOAudioStream |
*audioStream) |
{ |
UInt32 numSamplesLeft; |
float *floatDestBuf; |
SInt16 *inputBuf; |
// Note: Source is offset by firstSampleFrame |
inputBuf = &(((SInt16 *)sampleBuf)[firstSampleFrame * |
streamFormat->fNumChannels]); |
// Note: Destination is not. |
floatDestBuf = (float *)destBuf; |
numSamplesLeft = numSampleFrames * streamFormat->fNumChannels; |
const static float divisor = ( 1.0 / 32768 ); |
while (numSamplesLeft > 0) { |
SInt16 inputSample; |
inputSample = *inputBuf; |
if (inputSample >= 0) { |
*floatDestBuf = inputSample * divisor; |
} |
++inputBuf; |
++floatDestBuf; |
--numSamplesLeft; |
} |
return kIOReturnSuccess; |
} |
Код в Перечислении 3-13 делает следующие вещи:
Это запускается путем кастинга целевого буфера к a
float *
.Это бросает демонстрационный буфер к 16-разрядному целому числу со знаком и определяет начальную точку в этом входном буфере для преобразования.
Это вычисляет число фактических выборок для преобразования.
Это циклично выполняется посредством выборок, масштабируя каждого к в диапазоне –1.0 к 1,0 (таким образом преобразование его к плаванию) и хранение его в целевом буфере в надлежащем расположении.
Отладка и тестирование драйвера
Многие методы, которые Вы использовали бы в отладке и тестировании драйвера аудио, являются теми же, которые Вы использовали бы с другими типами драйверов устройств. В конце концов, любой драйвер Набора I/O имеет структуру и поведение, которые подобны любому другому драйверу Набора I/O, независимо от семьи.
Например, когда драйвер разрабатывается для создания, это всегда - хорошая идея IOLog
заходит в критические точки в Вашем коде, такой как прежде и после передачи I/O. IOLog
функционируйте пишет сообщение в консоль (доступный через Консольное приложение) и к /var/log/system.log
. Можно отформатировать строку сообщения с переменными данными в стиле printf
.
Точно так же можно исследовать Реестр I/O с приложением Проводника Реестра I/O или ioreg
утилита командной строки. Реестр I/O покажет позицию Ваших водительских объектов в штабеле драйвера, отношениях клиентского провайдера среди них и атрибутах тех объектов драйвера. На рисунке 3-2 Проводник Реестра I/O показывает часть объектов и их атрибутов в драйвере аудиоустройства USB.
Однако как Пользовательская Отладочная информация в Реестре I/O объясняет, Ваш драйвер может вставить информацию в Реестр I/O для помощи тестированию и отладке драйвера.
Инструменты для тестирования драйверов аудио
Пакет Разработчика OS X обеспечивает два приложения, которые полезны, когда Вы тестируете программное обеспечение драйвера аудио. Эти элементы не поставляются как исполнимые программы, но вместо этого включены как проекты примера кода, установленные в /Developer/Examples/CoreAudio/HAL
. Два проекта, представляющие интерес, являются HALLab и MillionMonkeys. Для получения исполнимых программ скопируйте папки проекта в корневой каталог (или любое расположение файловой системы, где у Вас есть доступ для записи), и разработайте проекты.
Приложение HALLab помогает Вам проверить средства управления и другие атрибуты загруженного драйвера аудио. С ним можно воспроизвести звуковые файлы к любому каналу устройства, проверить, ли отключение звука и работа изменений объема для каждого канала, протестируйте входную работу, включите мягкую игру через, просмотрите иерархию объектов устройства и сделайте различные другие тесты.
Рисунок 3-3 и рисунок 3-4 показывают Вам, на что похожи два из окон HALLab.
Приложение MillionMonkeys было разработано для профилирования производительности Вашего драйвера. В частности это позволяет Вам определять задержку на различных шагах обработки аудиоданных, в то время как система является объектом загрузки. Это может помочь в разыскивании связанных с производительностью проблем с драйверами аудио. Рисунок 3-5 и рисунок 3-6 показывают две области окна приложения MillionMonkeys.
Пользовательская отладочная информация в реестре I/O
Иначе можно протестировать и отладить драйвер аудиоустройства, должен записать пользовательские свойства в Реестр I/O. Например, можно хотеть отследить аппаратный регистр или внутреннее состояние драйвера состояния (если драйвер имеет кого-либо). Каждый раз, когда Ваш драйвер вносит изменение в аппаратное состояние, он мог считать аппаратные значения регистра и вызов setProperty
с текущей стоимостью. Затем когда тестирование драйвера, запускает приложение Проводника Реестра I/O и отмечает то, что Реестр I/O показывает этому значению, чтобы быть. Этот метод позволяет Вам легко определять, помещает ли драйвер аппаратные средства в корректное состояние.