Реализация драйвера аудио

Как обсуждено в главе Проект семьи Аудио, пишущий драйвер аудио с помощью семьи Audio требует, в объектно-ориентированных условиях, чтобы Вы сделали некоторые определенные вещи в своем коде:

Эта глава будет вести Вас через эти шаги реализации. Это использует в качестве источника кода проект SamplePCIAudioDriver в качестве примера (расположенный в /Developer/Examples/Kernel/IOKit/Audio/Templates когда Вы устанавливаете пакет Разработчика). В интересах краткости эта глава не использует весь код, найденный в том проекте, и разделяет комментарии от кода. Обратитесь к проекту SamplePCIAudioDriver для полного спектра кода, и комментирует его.

Установка проекта

Даже перед созданием проекта для драйвера аудио необходимо рассмотреть некоторые элементные фасеты проекта. Исследуйте аудио аппаратные средства и решите, какие объекты аудио семьи требуются, чтобы поддерживать их. Конечно, Ваш драйвер должен иметь тот IOAudioDevice объект (инстанцированный от пользовательского подкласса), но сколько IOAudioEngine, IOAudioStream, и IOAudioControl объекты необходимо ли создать?

Таблица 3-1 обеспечивает матрицу решения для определения, сколько объектов аудио семьи каждого вида, в котором Вы нуждаетесь.

Таблица 3-1  , Решающая, какая семья Audio возражает для создания (и другие проектные решения)

Вопрос

Что создать

Есть ли демонстрационные буферы различных размеров?

Создайте пользовательское IOAudioEngine объект для каждого демонстрационного буфера.

Сколько I/O или механизмы DMA находятся там на устройстве?

Создайте пользовательское IOAudioEngine объект для каждого I/O или механизма DMA.

Сколько отдельные или чередованные демонстрационные буферы там?

Создайте IOAudioStream объект для каждого буфера (оба ввода и вывода).

Сколько управляемые атрибуты там (объем, усиление, бесшумный режим, и т.д.)?

Создайте IOAudioControl объект для каждого атрибута.

Проект 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.

  Настройки Bundle рисунка 3-1 демонстрационного драйвера аудио PCI
Bundle settings of the sample PCI audio driver

Конечно, если бы Ваш водительский провайдер отличается (скажите, 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 то поведение реализации, определенное для трех функциональных типов управления. Инстанцируйте управления от подкласса, который является надлежащим управляемому атрибуту устройства.

Табличные 3-2  подклассы IOAudioControl

Подкласс

Цель

IOAudioLevelControl

Для средств управления, таких как объем, где диапазон измеримых значений (таких как децибелы) связан с целочисленным диапазоном.

IOAudioToggleControl

Для средств управления, таких как бесшумный режим, где состояние или выключено или включено.

IOAudioSelectorControl

Для средств управления, выбирающих дискретный атрибут, такой как входное усиление.

Каждый подкласс (или тип управления) имеет a create метод и удобный метод, определенный для подтипа управления. IOAudioTypes.h заголовочный файл, определяющий константы для типа управления и подтипа, также определяет другие константы, предназначенные, чтобы быть предоставленным как параметры в методах создания управления. Таблица 3-3 суммирует категории, в которые падают эти константы.

Табличные 3-3  Категории констант регулировки звука в IOAudioTypes.h

Категория

Цель

Примеры и комментарии

Ввести

Общая функция управления

Уровень, переключаются, или селектор (каждое соответствие IOAudioControl подкласс).

Подтип

Цель управления

Объем, бесшумный режим или ввод/вывод; удобные методы подкласса принимают подтип.

Идентификатор канала

Общие значения по умолчанию для каналов

Правильный канал по умолчанию, значение по умолчанию центрирует канал, значение по умолчанию 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-разрядной плавающей точкой. Однако аудио аппаратные средства обычно требуют, чтобы аудиоданные были в целочисленном формате.

Для выполнения этих преобразований драйвер аудио должен реализовать по крайней мере один из двух методов, в зависимости от направлений поддерживаемых аудиопотоков:

В дополнение к выполнению отсечения и преобразования, эти методы являются также хорошим местом для добавления специфичного для устройства кода фильтрации ввода и вывода. Например, определенная модель динамиков USB могла бы звучать лучше с небольшим высокочастотным спадом. (Обратите внимание на то, что, если это - единственная причина записи драйвера, необходимо обычно использовать AppleUSBAudio плагин вместо этого, как описано в примере кода SampleUSBAudioPlugin.)

Поскольку эти методы выполняют код с плавающей точкой, Вы не можете включать их в тот же исходный файл как другой IOAudioEngine методы Вы реализуете. Компилятор, по умолчанию, позволяет эмуляции с плавающей точкой препятствовать тому, чтобы были сгенерированы инструкции с плавающей точкой. Для обхождения этого создайте отдельную библиотеку, содержащую код с плавающей точкой, и скомпилируйте и соедините эту библиотеку в получающийся модуль ядра. Отдельная библиотека для проекта SamplePCIAudioDriver libAudioFloatLib.

Частой ошибке, которую люди делают при разработке драйвера аудио, или не удается записать эти методы или не удающийся включать эту дополнительную библиотеку при соединении KEXT. Когда это произойдет, Вы выполнитесь clipOutputSamples и convertInputSamples методы, встроенные в базовый класс. Эти методы являются просто тупиками тот возврат kIOReturnUnsupported (0xe00002c7, или -536870201). Если Вы видите эту ошибку, возвращенную одним из этих методов, необходимо удостовериться, что Вы соединяете свой KEXT правильно.

clipOutputSamples метод передается шесть параметров:

Ваша реализация должна сначала отсечь любые выборки с плавающей точкой в буфере соединения, выходящие за пределы диапазона –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;
}

Вот несколько комментариев к этому определенному примеру:

  1. Это запускается путем кастинга void * буферы к float * для буфера соединения и SInt16 * для демонстрационного буфера; в то время как буфер соединения всегда, в этом проекте аппаратное использование подписало 16-разрядные целые числа для его выборок float *.

  2. Затем, это вычисляет верхний предел демонстрационного индекса для предстоящего отсечения и преобразования цикла.

  3. Циклы метода через соединение и выборку буферизуют и выполняют клип и операции преобразования на одной выборке за один раз.

    1. Это выбирает выборку с плавающей точкой от буфера соединения и отсекает его (если необходимый) к диапазону между-1.0 и 1.0.

    2. Это масштабирует и преобразовывает значение с плавающей точкой в надлежащую 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 делает следующие вещи:

  1. Это запускается путем кастинга целевого буфера к a float *.

  2. Это бросает демонстрационный буфер к 16-разрядному целому числу со знаком и определяет начальную точку в этом входном буфере для преобразования.

  3. Это вычисляет число фактических выборок для преобразования.

  4. Это циклично выполняется посредством выборок, масштабируя каждого к в диапазоне –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.

Рисунок 3-2  Реестр I/O (через Проводник Реестра I/O)
The I/O Registry (via I/O Registry Explorer)

Однако как Пользовательская Отладочная информация в Реестре I/O объясняет, Ваш драйвер может вставить информацию в Реестр I/O для помощи тестированию и отладке драйвера.

Инструменты для тестирования драйверов аудио

Пакет Разработчика OS X обеспечивает два приложения, которые полезны, когда Вы тестируете программное обеспечение драйвера аудио. Эти элементы не поставляются как исполнимые программы, но вместо этого включены как проекты примера кода, установленные в /Developer/Examples/CoreAudio/HAL. Два проекта, представляющие интерес, являются HALLab и MillionMonkeys. Для получения исполнимых программ скопируйте папки проекта в корневой каталог (или любое расположение файловой системы, где у Вас есть доступ для записи), и разработайте проекты.

Приложение HALLab помогает Вам проверить средства управления и другие атрибуты загруженного драйвера аудио. С ним можно воспроизвести звуковые файлы к любому каналу устройства, проверить, ли отключение звука и работа изменений объема для каждого канала, протестируйте входную работу, включите мягкую игру через, просмотрите иерархию объектов устройства и сделайте различные другие тесты.

Рисунок 3-3 и рисунок 3-4 показывают Вам, на что похожи два из окон HALLab.

  
The HALLab System window
  Окно Figure 3-4 The HALLab IO Cycle Telemetry окна The HALLab System рисунка 3-3
The HALLab IO Cycle Telemetry windowThe HALLab IO Cycle Telemetry window

Приложение MillionMonkeys было разработано для профилирования производительности Вашего драйвера. В частности это позволяет Вам определять задержку на различных шагах обработки аудиоданных, в то время как система является объектом загрузки. Это может помочь в разыскивании связанных с производительностью проблем с драйверами аудио. Рисунок 3-5 и рисунок 3-6 показывают две области окна приложения MillionMonkeys.

  
The MillionMonkeys Device & Workload paneThe MillionMonkeys Device & Workload pane
  Область Figure 3-6 The MillionMonkeys Data Collection & Display области The MillionMonkeys Device & Workload рисунка 3-5
The MillionMonkeys Data Collection & Display paneThe MillionMonkeys Data Collection & Display pane

Пользовательская отладочная информация в реестре I/O

Иначе можно протестировать и отладить драйвер аудиоустройства, должен записать пользовательские свойства в Реестр I/O. Например, можно хотеть отследить аппаратный регистр или внутреннее состояние драйвера состояния (если драйвер имеет кого-либо). Каждый раз, когда Ваш драйвер вносит изменение в аппаратное состояние, он мог считать аппаратные значения регистра и вызов setProperty с текущей стоимостью. Затем когда тестирование драйвера, запускает приложение Проводника Реестра I/O и отмечает то, что Реестр I/O показывает этому значению, чтобы быть. Этот метод позволяет Вам легко определять, помещает ли драйвер аппаратные средства в корректное состояние.