---
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/#.
