--- description: 'Звуковая подсистема FreeBSD' next: books/arch-handbook/pccard params: path: /books/arch-handbook/sound/ prev: books/arch-handbook/newbus showBookMenu: 'true' tags: ["Sound", "OSS", "pcm", "mixer"] title: 'Глава 15. Звуковая подсистема' weight: 17 --- [[oss]] = Звуковая подсистема :doctype: book :toc: macro :toclevels: 1 :icons: font :sectnums: :sectnumlevels: 6 :sectnumoffset: 15 :partnums: :source-highlighter: rouge :experimental: :images-path: books/arch-handbook/ ifdef::env-beastie[] ifdef::backend-html5[] :imagesdir: ../../../../images/{images-path} endif::[] ifndef::book[] include::shared/authors.adoc[] include::shared/mirrors.adoc[] include::shared/releases.adoc[] include::shared/attributes/attributes-{{% lang %}}.adoc[] include::shared/{{% lang %}}/teams.adoc[] include::shared/{{% lang %}}/mailing-lists.adoc[] include::shared/{{% lang %}}/urls.adoc[] toc::[] endif::[] ifdef::backend-pdf,backend-epub3[] include::../../../../../shared/asciidoctor.adoc[] endif::[] endif::[] ifndef::env-beastie[] toc::[] include::../../../../../shared/asciidoctor.adoc[] endif::[] [[oss-intro]] == Введение Подсистема звука FreeBSD чётко разделяет общие вопросы обработки звука и детали, специфичные для устройств. Это упрощает добавление поддержки нового оборудования. man:pcm[4] — это центральный компонент подсистемы звука. В основном он реализует следующие элементы: * Интерфейс системных вызовов (read, write, ioctls) для работы с оцифрованным звуком и функциями микшера. Набор команд ioctl совместим с устаревшим интерфейсом _OSS_ или _Voxware_, что позволяет портировать распространённые мультимедийные приложения без изменений. * Общий код для обработки звуковых данных (преобразование форматов, виртуальные каналы). * Единый программный интерфейс к аппаратно-зависимым модулям аудиоинтерфейсов. * Дополнительная поддержка некоторых распространённых аппаратных интерфейсов (ac97) или общий код для специфичного оборудования (например: подпрограммы ISA DMA). Поддержка конкретных звуковых карт реализована аппаратно-специфичными драйверами, которые предоставляют интерфейсы каналов и микшера для подключения к общему коду [.filename]#pcm#. В этой главе термин [.filename]#pcm# будет относиться к центральной, общей части звукового драйвера, в отличие от аппаратно-зависимых модулей. Разработчик драйверов, только начинающий свою разработку, конечно, захочет начать с существующего модуля и использовать его код в качестве основного источника информации. Однако, хотя код подсистемы звука чист и аккуратен, он в основном лишён комментариев. Этот документ пытается дать обзор интерфейса фреймворка и ответить на некоторые вопросы, которые могут возникнуть при адаптации существующего кода. В качестве альтернативы или в дополнение к началу разработки с примера драйвера из кода системы, вы можете найти шаблон драйвера с комментариями по адресу https://people.FreeBSD.org/~cg/template.c[ https://people.FreeBSD.org/~cg/template.c] [[oss-files]] == Файлы Весь соответствующий код находится в [.filename]#/usr/src/sys/dev/sound/#, за исключением определений публичного интерфейса ioctl, которые можно найти в [.filename]#/usr/src/sys/sys/soundcard.h# В каталоге [.filename]#/usr/src/sys/dev/sound/#, папка [.filename]#pcm/# содержит основной код, тогда как каталоги [.filename]#pci/#, [.filename]#isa/# и [.filename]#usb/# содержат драйверы для плат PCI и ISA, а также для USB-аудиоустройств. [[pcm-probe-and-attach]] == Обнаружение, подключение и т.д. Драйверы звуковых устройств выполняют обнаружение и подключение почти так же, как и любой модуль драйвера оборудования. Возможно, вам будет полезно ознакомиться с разделами руководства, посвящёнными crossref:isa-driver[isa-driver,ISA] или crossref:pci[pci,PCI], для получения дополнительной информации. Однако драйверы звука отличаются в некоторых аспектах: * Они объявляют себя как устройства класса [.filename]#pcm#, с приватной структурой устройства `struct snddev_info`: + [.programlisting] .... 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). Её адрес передаётся в [.filename]#pcm# через вызовы `pcm_register()` и `mixer_init()`. [.filename]#pcm# позже передаёт обратно этот адрес в качестве параметра при вызовах интерфейсов звукового драйвера. * Процедура подключения (attach) звукового драйвера должна объявить свой интерфейс MIXER или AC97 для [.filename]#pcm#, вызвав `mixer_init()`. Для интерфейса MIXER это, в свою очередь, приводит к вызову crossref:sound[xxxmixer-init,`xxxmixer_init()`]. * Процедура подключения драйвера звука объявляет свою общую конфигурацию CHANNEL для [.filename]#pcm#, вызывая `pcm_register(dev, sc, nplay, nrec)`, где `sc` — это адрес структуры данных устройства, используемый при последующих вызовах из [.filename]#pcm#, а `nplay` и `nrec` — количество каналов воспроизведения и записи. * Подпрограмма подключения звукового драйвера объявляет каждый из своих каналов вызовами `pcm_addchan()`. Это настраивает связующий слой канала в [.filename]#pcm# и, в свою очередь, вызывает вызов crossref:sound[xxxchannel-init,`xxxchannel_init()`]. * Драйвер звука должен вызвать `pcm_unregister()` в процедуре отключения (detach) перед освобождением своих ресурсов. Существует два возможных способа работы с устройствами, не поддерживающими PnP: * Используйте метод `device_identify()` (пример: [.filename]#sound/isa/es1888.c#). Метод `device_identify()` проверяет наличие оборудования по известным адресам и, если находит поддерживаемое устройство, создает новое pcm-устройство, которое затем передаётся для probe/attach. * Используйте пользовательскую конфигурацию ядра с соответствующими подсказками для устройств pcm (пример: [.filename]#sound/isa/mss.c#). [.filename]#pcm# драйверы должны реализовывать подпрограммы `device_suspend`, `device_resume` и `device_shutdown`, чтобы управление питанием и выгрузка модулей работали корректно. [[oss-interfaces]] == Интерфейсы Интерфейс между ядром [.filename]#pcm# и звуковыми драйверами определяется в терминах crossref:kobj[kernel-objects,объектов ядра Kobj]. Существует два основных интерфейса, которые обычно предоставляет драйвер звука: _CHANNEL_ и либо _MIXER_, либо _AC97_. Интерфейс _AC97_ — это очень небольшой интерфейс доступа к оборудованию (чтение/запись регистров), реализованный драйверами для устройств с кодеком AC97. В этом случае фактический интерфейс MIXER предоставляется общим кодом AC97 в [.filename]#pcm#. === Интерфейс CHANNEL ==== Общие примечания для параметров функций Драйверы звука обычно имеют приватную структуру данных для описания своего устройства и по одной структуре для каждого канала воспроизведения и записи, который они поддерживают. Для всех функций интерфейса CHANNEL первый параметр — это непрозрачный указатель. Второй параметр представляет собой указатель на приватную структуру данных канала, за исключением `channel_init()`, где передаётся указатель на приватную структуру устройства (и возвращается указатель на канал для дальнейшего использования [.filename]#pcm#). ==== Обзор операций передачи данных Для надёжной передачи звуковых данных ядро [.filename]#pcm# и драйверы звука взаимодействуют через общую область памяти, описываемую структурой `struct snd_dbuf`. `struct snd_dbuf` является приватной для [.filename]#pcm#, и драйверы звука получают нужные значения через вызовы функций доступа (`sndbuf_getxxx()`). Область разделяемой памяти имеет размер `sndbuf_getsize()` и разделена на блоки фиксированного размера по `sndbuf_getblksz()` байт. При воспроизведении общий механизм передачи выглядит следующим образом (для записи идея обратная): * [.filename]#pcm# сначала заполняет буфер, затем вызывает функцию crossref:sound[channel-trigger,`xxxchannel_trigger()`] драйвера звука с параметром PCMTRIG_START. * Звуковой драйвер затем организует повторяющуюся передачу всей области памяти (`sndbuf_getbuf()`, `sndbuf_getsize()`) на устройство блоками по `sndbuf_getblksz()` байт. Для каждого переданного блока он вызывает функцию `chn_intr()`[.filename]#pcm# (обычно это происходит во время прерывания). * `chn_intr()` организует копирование новых данных в область, которая была передана устройству (теперь свободна), и вносит соответствующие обновления в структуру `snd_dbuf`. [[xxxchannel-init]] ==== channel_init `xxxchannel_init()` вызывается для инициализации каждого из каналов воспроизведения или записи. Вызовы инициируются из процедуры подключения драйвера звука. (См. раздел crossref:sound[pcm-probe-and-attach,Обнаружение и подключение]). [.programlisting] .... static void * xxxchannel_init(kobj_t obj, void *data, struct snd_dbuf *b, struct pcm_channel *c, int dir) <.> { struct xxx_info *sc = data; struct xxx_chinfo *ch; ... return ch; <.> } .... <.> `b` — это адрес для канала `struct snd_dbuf`. Он должен быть инициализирован в функции вызовом `sndbuf_alloc()`. Размер буфера, который следует использовать, обычно представляет собой небольшое кратное от 'типичного' размера единицы передачи данных для вашего устройства. `c` — это указатель на структуру управления каналом [.filename]#pcm#. Это непрозрачный объект. Функция должна сохранить его в локальной структуре канала для использования в последующих вызовах [.filename]#pcm# (например: `chn_intr(c)`). `dir` указывает направление канала (`PCMDIR_PLAY` или `PCMDIR_REC`). <.> Функция должна возвращать указатель на приватную область, используемую для управления этим каналом. Этот указатель будет передаваться в качестве параметра при других вызовах интерфейса канала. ==== channel_setformat `xxxchannel_setformat()` должен настроить оборудование для указанного канала под указанный звуковой формат. [.programlisting] .... static int xxxchannel_setformat(kobj_t obj, void *data, u_int32_t format) <.> { struct xxx_chinfo *ch = data; ... return 0; } .... <.> `format` указывается как значение `AFMT_XXX` ([.filename]#soundcard.h#). ==== channel_setspeed `xxxchannel_setspeed()` настраивает оборудование канала для указанной скорости дискретизации и возвращает возможно скорректированную скорость. [.programlisting] .... static int xxxchannel_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct xxx_chinfo *ch = data; ... return speed; } .... ==== channel_setblocksize `xxxchannel_setblocksize()` устанавливает размер блока, который является размером единичных транзакций между [.filename]#pcm# и звуковым драйвером, а также между звуковым драйвером и устройством. Обычно это количество байт, передаваемых до возникновения прерывания. Во время передачи звуковой драйвер должен вызывать `chn_intr()` из [.filename]#pcm# каждый раз, когда передаётся данный размер. Большинство драйверов звука здесь учитывают только размер блока, который будет использоваться при начале фактической передачи. [.programlisting] .... static int xxxchannel_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct xxx_chinfo *ch = data; ... return blocksize; <.> } .... <.> Функция возвращает, возможно, скорректированный размер блока. Если размер блока действительно изменён, следует вызвать `sndbuf_resize()` для корректировки буфера. [[channel-trigger]] ==== channel_trigger `xxxchannel_trigger()` вызывается [.filename]#pcm# для управления операциями передачи данных в драйвере. [.programlisting] .... static int xxxchannel_trigger(kobj_t obj, void *data, int go) <.> { struct xxx_chinfo *ch = data; ... return 0; } .... <.> `go` определяет действие для текущего вызова. Возможные значения: [NOTE] ==== Если драйвер использует ISA DMA, перед выполнением действий с устройством следует вызвать `sndbuf_isadma()`, которая позаботится о том, что делает DMA-чип. ==== ==== channel_getptr `xxxchannel_getptr()` возвращает текущее смещение в буфере передачи. Обычно этот вызов выполняется функцией `chn_intr()`, и именно так [.filename]#pcm# узнаёт, куда можно передавать новые данные. ==== channel_free `xxxchannel_free()` вызывается для освобождения ресурсов канала, например, при выгрузке драйвера, и должна быть реализована, если структуры данных канала динамически выделены или если `sndbuf_alloc()` не использовалась для выделения буфера. ==== channel_getcaps [.programlisting] .... struct pcmchan_caps * xxxchannel_getcaps(kobj_t obj, void *data) { return &xxx_caps; <.> } .... <.> Подпрограмма возвращает указатель на (обычно статически определённую) структуру `pcmchan_caps` (определена в [.filename]#sound/pcm/channel.h#). Эта структура содержит минимальную и максимальную частоты дискретизации, а также поддерживаемые звуковые форматы. Пример можно найти в любом драйвере звукового устройства. ==== Дополнительные функции `channel_reset()`, `channel_resetdone()` и `channel_notify()` предназначены для специальных целей и не должны реализовываться в драйвере без обсуждения на {freebsd-multimedia}. `channel_setdir()` устарела. === Интерфейс MIXER [[xxxmixer-init]] ==== mixer_init `xxxmixer_init()` инициализирует оборудование и сообщает [.filename]#pcm#, какие устройства микшера доступны для воспроизведения и записи [.programlisting] .... 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] <.> mix_setdevs(m, v); [Set appropriate bits in v for record mixers] mix_setrecdevs(m, v) return 0; } .... <.> Установите биты в целочисленном значении и вызовите `mix_setdevs()` и `mix_setrecdevs()`, чтобы сообщить [.filename]#pcm#, какие устройства существуют. Определения битов микшера можно найти в [.filename]#soundcard.h# (значения `SOUND_MASK_XXX` и сдвиги битов `SOUND_MIXER_XXX`). ==== mixer_set `xxxmixer_set()` устанавливает уровень громкости для одного устройства микшера. [.programlisting] .... static int xxxmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) <.> { struct sc_info *sc = mix_getdevinfo(m); [set volume level] return left | (right << 8); <.> } .... <.> Устройство указывается как значение `SOUND_MIXER_XXX`. Значения громкости задаются в диапазоне [0-100]. Значение ноль должно отключать звук устройства. <.> Поскольку уровни оборудования, вероятно, не совпадут с входной шкалой и будет происходить округление, процедура возвращает фактические значения уровней (в диапазоне 0-100), как показано. ==== mixer_setrecsrc `xxxmixer_setrecsrc()` устанавливает устройство источника записи. [.programlisting] .... static int xxxmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) <.> { 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; <.> } .... <.> Желаемые устройства записи указываются в виде битового поля <.> Возвращаются фактические устройства, настроенные для записи. Некоторые драйверы могут настраивать только одно устройство для записи. Функция должна возвращать -1 в случае ошибки. ==== mixer_uninit, mixer_reinit `xxxmixer_uninit()` должен гарантировать, что весь звук отключен, и, если возможно, аппаратный микшер должен быть переведен в режим пониженного энергопотребления. `xxxmixer_reinit()` должна гарантировать, что аппаратура микшера включена и все настройки, не управляемые `mixer_set()` или `mixer_setrecsrc()`, восстановлены. === Интерфейс AC97 Интерфейс _AC97_ реализован драйверами с кодеком AC97. У него есть только три метода: * `xxxac97_init()` возвращает количество найденных кодеков ac97. * `ac97_read()` и `ac97_write()` читают или записывают указанный регистр. Интерфейс _AC97_ используется кодом AC97 в [.filename]#pcm# для выполнения операций более высокого уровня. В качестве примера можно посмотреть [.filename]#sound/pci/maestro3.c# или другие файлы в [.filename]#sound/pci/#.