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