Глава 15. Звуковая подсистема

Этот перевод может быть устаревшим. Для того, чтобы помочь с переводом, пожалуйста, обратитесь к Сервер переводов FreeBSD.

15.1. Введение

Подсистема звука FreeBSD чётко разделяет общие вопросы обработки звука и детали, специфичные для устройств. Это упрощает добавление поддержки нового оборудования.

pcm(4) — это центральный компонент подсистемы звука. В основном он реализует следующие элементы:

  • Интерфейс системных вызовов (read, write, ioctls) для работы с оцифрованным звуком и функциями микшера. Набор команд ioctl совместим с устаревшим интерфейсом OSS или Voxware, что позволяет портировать распространённые мультимедийные приложения без изменений.

  • Общий код для обработки звуковых данных (преобразование форматов, виртуальные каналы).

  • Единый программный интерфейс к аппаратно-зависимым модулям аудиоинтерфейсов.

  • Дополнительная поддержка некоторых распространённых аппаратных интерфейсов (ac97) или общий код для специфичного оборудования (например: подпрограммы ISA DMA).

Поддержка конкретных звуковых карт реализована аппаратно-специфичными драйверами, которые предоставляют интерфейсы каналов и микшера для подключения к общему коду pcm.

В этой главе термин pcm будет относиться к центральной, общей части звукового драйвера, в отличие от аппаратно-зависимых модулей.

Разработчик драйверов, только начинающий свою разработку, конечно, захочет начать с существующего модуля и использовать его код в качестве основного источника информации. Однако, хотя код подсистемы звука чист и аккуратен, он в основном лишён комментариев. Этот документ пытается дать обзор интерфейса фреймворка и ответить на некоторые вопросы, которые могут возникнуть при адаптации существующего кода.

В качестве альтернативы или в дополнение к началу разработки с примера драйвера из кода системы, вы можете найти шаблон драйвера с комментариями по адресу https://people.FreeBSD.org/~cg/template.c

15.2. Файлы

Весь соответствующий код находится в /usr/src/sys/dev/sound/, за исключением определений публичного интерфейса ioctl, которые можно найти в /usr/src/sys/sys/soundcard.h

В каталоге /usr/src/sys/dev/sound/, папка pcm/ содержит основной код, тогда как каталоги pci/, isa/ и usb/ содержат драйверы для плат PCI и ISA, а также для USB-аудиоустройств.

15.3. Обнаружение, присоединение и т.д.

Драйверы звуковых устройств выполняют обнаружение и подключение почти так же, как и любой модуль драйвера оборудования. Возможно, вам будет полезно ознакомиться с разделами руководства, посвящёнными ISA или PCI, для получения дополнительной информации.

Однако драйверы звука отличаются в некоторых аспектах:

  • Они объявляют себя как устройства класса pcm, с приватной структурой устройства struct snddev_info:

              static driver_t xxx_driver = {
                  "pcm",
                  xxx_methods,
                  sizeof(struct snddev_info)
              };
    
              DRIVER_MODULE(snd_xxxpci, pci, xxx_driver, pcm_devclass, 0, 0);
              MODULE_DEPEND(snd_xxxpci, snd_pcm, PCM_MINVER, PCM_PREFVER,PCM_MAXVER);

    Большинству звуковых драйверов необходимо хранить дополнительную приватную информацию о своём устройстве. Приватная структура данных обычно выделяется в процедуре attach. Её адрес передаётся в pcm через вызовы pcm_register() и mixer_init(). pcm позже передаёт обратно этот адрес в качестве параметра при вызовах интерфейсов звукового драйвера.

  • Подпрограмма подключения звукового драйвера должна объявить свой интерфейс MIXER или AC97 для pcm, вызвав mixer_init(). Для интерфейса MIXER это, в свою очередь, приводит к вызову xxxmixer_init().

  • Функция подключения драйвера звука объявляет свою общую конфигурацию CHANNEL для pcm, вызывая pcm_register(dev, sc, nplay, nrec), где sc — это адрес структуры данных устройства, используемый при последующих вызовах из pcm, а nplay и nrec — количество каналов воспроизведения и записи.

  • Подпрограмма подключения звукового драйвера объявляет каждый из своих каналов вызовами pcm_addchan(). Это настраивает связующий слой канала в pcm и, в свою очередь, вызывает вызов xxxchannel_init().

  • Драйвер звука должен вызвать pcm_unregister() в процедуре отключения перед освобождением своих ресурсов.

Существует два возможных способа работы с устройствами, не поддерживающими PnP:

  • Используйте метод device_identify() (пример: sound/isa/es1888.c). Метод device_identify() проверяет наличие оборудования по известным адресам и, если находит поддерживаемое устройство, создает новое pcm-устройство, которое затем передается для probe/attach.

  • Используйте пользовательскую конфигурацию ядра с соответствующими подсказками для устройств pcm (пример: sound/isa/mss.c).

pcm драйверы должны реализовывать подпрограммы device_suspend, device_resume и device_shutdown, чтобы управление питанием и выгрузка модулей работали корректно.

15.4. Интерфейсы

Интерфейс между ядром pcm и звуковыми драйверами определяется в терминах объектов ядра Kobj.

Существует два основных интерфейса, которые обычно предоставляет драйвер звука: CHANNEL и либо MIXER, либо AC97.

Интерфейс AC97 — это очень небольшой интерфейс доступа к оборудованию (чтение/запись регистров), реализованный драйверами для устройств с кодеком AC97. В этом случае фактический интерфейс MIXER предоставляется общим кодом AC97 в pcm.

15.4.1. Интерфейс CHANNEL

15.4.1.1. Общие примечания для параметров функций

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

Для всех функций интерфейса CHANNEL первый параметр — это непрозрачный указатель.

Второй параметр представляет собой указатель на приватную структуру данных канала, за исключением channel_init(), где передается указатель на приватную структуру устройства (и возвращается указатель на канал для дальнейшего использования pcm).

15.4.1.2. Обзор операций передачи данных

Для надежной передачи звуковых данных ядро pcm и драйверы звука взаимодействуют через общую область памяти, описываемую структурой struct snd_dbuf.

struct snd_dbuf является приватной для pcm, и драйверы звука получают нужные значения через вызовы функций доступа (sndbuf_getxxx()).

Область разделяемой памяти имеет размер sndbuf_getsize() и разделена на блоки фиксированного размера по sndbuf_getblksz() байт.

При воспроизведении общий механизм передачи выглядит следующим образом (для записи идея обратная):

  • pcm сначала заполняет буфер, затем вызывает функцию xxxchannel_trigger() драйвера звука с параметром PCMTRIG_START.

  • Звуковой драйвер затем организует повторяющуюся передачу всей области памяти (sndbuf_getbuf(), sndbuf_getsize()) на устройство блоками по sndbuf_getblksz() байт. Для каждого переданного блока он вызывает функцию chn_intr()pcm (обычно это происходит во время прерывания).

  • chn_intr() организует копирование новых данных в область, которая была передана устройству (теперь свободна), и вносит соответствующие обновления в структуру snd_dbuf.

15.4.1.3. channel_init

xxxchannel_init() вызывается для инициализации каждого из каналов воспроизведения или записи. Вызовы инициируются из процедуры присоединения драйвера звука. (См. раздел зондирование и присоединение).

          static void *
          xxxchannel_init(kobj_t obj, void *data,
             struct snd_dbuf *b, struct pcm_channel *c, int dir) (1)
          {
              struct xxx_info *sc = data;
              struct xxx_chinfo *ch;
               ...
              return ch; (2)
           }
1b — это адрес для канала struct snd_dbuf. Он должен быть инициализирован в функции вызовом sndbuf_alloc(). Размер буфера, который следует использовать, обычно представляет собой небольшое кратное от 'типичного' размера единицы передачи данных для вашего устройства. c — это указатель на структуру управления каналом pcm. Это непрозрачный объект. Функция должна сохранить его в локальной структуре канала для использования в последующих вызовах pcm (например: chn_intr(c)). dir указывает направление канала (PCMDIR_PLAY или PCMDIR_REC).
2Функция должна возвращать указатель на приватную область, используемую для управления этим каналом. Этот указатель будет передаваться в качестве параметра при других вызовах интерфейса канала.

15.4.1.4. channel_setformat

xxxchannel_setformat() должен настроить оборудование для указанного канала под указанный звуковой формат.

          static int
          xxxchannel_setformat(kobj_t obj, void *data, u_int32_t format) (1)
          {
              struct xxx_chinfo *ch = data;
               ...
              return 0;
           }
1format указывается как значение AFMT_XXX (soundcard.h).

15.4.1.5. channel_setspeed

xxxchannel_setspeed() настраивает оборудование канала для указанной скорости дискретизации и возвращает возможно скорректированную скорость.

          static int
          xxxchannel_setspeed(kobj_t obj, void *data, u_int32_t speed)
          {
              struct xxx_chinfo *ch = data;
               ...
              return speed;
           }

15.4.1.6. channel_setblocksize

xxxchannel_setblocksize() устанавливает размер блока, который является размером единичных транзакций между pcm и звуковым драйвером, а также между звуковым драйвером и устройством. Обычно это количество байт, передаваемых до возникновения прерывания. Во время передачи звуковой драйвер должен вызывать chn_intr() из pcm каждый раз, когда передается данный размер.

Большинство драйверов звука здесь учитывают только размер блока, который будет использоваться при начале фактической передачи.

          static int
          xxxchannel_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
          {
              struct xxx_chinfo *ch = data;
                ...
              return blocksize; (1)
           }
1Функция возвращает, возможно, скорректированный размер блока. Если размер блока действительно изменён, следует вызвать sndbuf_resize() для корректировки буфера.

15.4.1.7. channel_trigger

xxxchannel_trigger() вызывается pcm для управления операциями передачи данных в драйвере.

          static int
          xxxchannel_trigger(kobj_t obj, void *data, int go) (1)
          {
              struct xxx_chinfo *ch = data;
               ...
              return 0;
           }
1go определяет действие для текущего вызова. Возможные значения:

Если драйвер использует ISA DMA, перед выполнением действий с устройством следует вызвать sndbuf_isadma(), которая позаботится о том, что делает DMA-чип.

15.4.1.8. channel_getptr

xxxchannel_getptr() возвращает текущее смещение в буфере передачи. Обычно этот вызов выполняется функцией chn_intr(), и именно так pcm узнаёт, куда можно передавать новые данные.

15.4.1.9. channel_free

xxxchannel_free() вызывается для освобождения ресурсов канала, например, при выгрузке драйвера, и должна быть реализована, если структуры данных канала динамически выделены или если sndbuf_alloc() не использовалась для выделения буфера.

15.4.1.10. channel_getcaps

          struct pcmchan_caps *
          xxxchannel_getcaps(kobj_t obj, void *data)
          {
              return &xxx_caps; (1)
           }
1Подпрограмма возвращает указатель на (обычно статически определённую) структуру pcmchan_caps (определена в sound/pcm/channel.h). Эта структура содержит минимальную и максимальную частоты дискретизации, а также поддерживаемые звуковые форматы. Пример можно найти в любом драйвере звукового устройства.

15.4.1.11. Дополнительные функции

channel_reset(), channel_resetdone() и channel_notify() предназначены для специальных целей и не должны реализовываться в драйвере без обсуждения на Список рассылки, посвящённый поддержке средств мультимедиа под FreeBSD.

channel_setdir() устарела.

15.4.2. Интерфейс MIXER

15.4.2.1. mixer_init

xxxmixer_init() инициализирует оборудование и сообщает pcm, какие устройства микшера доступны для воспроизведения и записи

          static int
          xxxmixer_init(struct snd_mixer *m)
          {
              struct xxx_info   *sc = mix_getdevinfo(m);
              u_int32_t v;

              [Initialize hardware]

              [Set appropriate bits in v for play mixers] (1)
              mix_setdevs(m, v);
              [Set appropriate bits in v for record mixers]
              mix_setrecdevs(m, v)

              return 0;
          }
1Установите биты в целочисленном значении и вызовите mix_setdevs() и mix_setrecdevs(), чтобы сообщить pcm, какие устройства существуют.

Определения битов микшера можно найти в soundcard.h (значения SOUND_MASK_XXX и сдвиги битов SOUND_MIXER_XXX).

15.4.2.2. mixer_set

xxxmixer_set() устанавливает уровень громкости для одного устройства микшера.

          static int
          xxxmixer_set(struct snd_mixer *m, unsigned dev,
                           unsigned left, unsigned right) (1)
          {
              struct sc_info *sc = mix_getdevinfo(m);
              [set volume level]
              return left | (right << 8); (2)
          }
1Устройство указывается как значение SOUND_MIXER_XXX. Значения громкости задаются в диапазоне [0-100]. Значение ноль должно отключать звук устройства.
2Поскольку уровни оборудования, вероятно, не совпадут с входной шкалой и будет происходить округление, процедура возвращает фактические значения уровней (в диапазоне 0-100), как показано.

15.4.2.3. mixer_setrecsrc

xxxmixer_setrecsrc() устанавливает устройство источника записи.

          static int
          xxxmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) (1)
          {
              struct xxx_info *sc = mix_getdevinfo(m);

              [look for non zero bit(s) in src, set up hardware]

              [update src to reflect actual action]
              return src; (2)
           }
1Желаемые устройства записи указываются в виде битового поля
2Возвращаются фактические устройства, настроенные для записи. Некоторые драйверы могут настраивать только одно устройство для записи. Функция должна возвращать -1 в случае ошибки.

15.4.2.4. mixer_uninit, mixer_reinit

xxxmixer_uninit() должен гарантировать, что весь звук отключен, и, если возможно, аппаратный микшер должен быть переведен в режим пониженного энергопотребления.

xxxmixer_reinit() должна гарантировать, что аппаратура микшера включена и все настройки, не управляемые mixer_set() или mixer_setrecsrc(), восстановлены.

15.4.3. Интерфейс AC97

Интерфейс AC97 реализован драйверами с кодеком AC97. У него есть только три метода:

  • xxxac97_init() возвращает количество найденных кодеков ac97.

  • ac97_read() и ac97_write() читают или записывают указанный регистр.

Интерфейс AC97 используется кодом AC97 в pcm для выполнения операций более высокого уровня. В качестве примера можно посмотреть sound/pci/maestro3.c или другие файлы в sound/pci/.


Изменено: 14 октября 2025 г. by Vladlen Popolitov