Глава 15. Звуковая подсистема | FreeBSD Documentation Portal
Description: Звуковая подсистема FreeBSD
Keywords: Sound,OSS,pcm,mixer

# Глава 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 позже передаёт обратно этот адрес в качестве параметра при вызовах интерфейсов звукового драйвера.

- Процедура подключения (attach) звукового драйвера должна объявить свой интерфейс 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()` в процедуре отключения (detach) перед освобождением своих ресурсов.

Существует два возможных способа работы с устройствами, не поддерживающими 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)
               }

1 | `b` — это адрес для канала `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;
               }

1 | `format` указывается как значение `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;
               }

1 | `go` определяет действие для текущего вызова. Возможные значения:

| Если драйвер использует 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/.

Изменено: 17 мая 2026 г. by Vladlen Popolitov

Назад
Главная
Далее