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);
Глава 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
:Большинству звуковых драйверов необходимо хранить дополнительную приватную информацию о своём устройстве. Приватная структура данных обычно выделяется в процедуре 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) }
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, перед выполнением действий с устройством следует вызвать |
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