Конспект по конфигурированию звуковой подсистемы ALSA (2019 г)

ALSA - Advanced Linux Sound Architecture. Набор драйверов, ядерных подсистем и утилит для поддержки звука под линухом.
Официальный сайт: https://alsa-project.org/wiki/Main_Page
Тут интересное  : https://alsa-project.org/wiki/Documentation
Тоже глянуть	: https://www.volkerschatz.com/noise/alsa.html
		: https://wiki.archlinux.org/index.php/Advanced_Linux_Sound_Architecture

ALSA заменила OSS (более старую юниховую подсистему звука - Open Sound System), но, в, строгом соответствии традициям,
добавив кучу новых фич, она стала гораздо гораздо менее понятной как для пользователя так и для программиста.
Единственный документ, описывающий работу OSS, занимал < 100 страниц и был предельно прост.
По нему можно было написать прогу и она просто работала без вариантов (во всяком случае под FreeBSD).
Файлов конфигурации к OSS просто не было. Два канала на устройство, ввод/вывод, остальное - излишества.
Хотя, надо сказать, с развитием OSS под фряхой добавились и микширование вывода с разных прог и виртуальные регуляторы громкости
приложений и управление роутингом и даже возможность это всё выключить нафиг. Без файлов конфигов, просто через переменные sysctl.

К ALSA и на английском-то мало доков и чёткой структуры между ними не просматривается,
а на русском вообще только примеры настройки без комментариев (зачастую, скопированные с тех же немногочисленных англоязычных источников).
Зато у неё очень много разных сущностей, комбинируя которые можно делать самые безумные и бессмысленные вещи.

Здесь будет рассматриваться только алса, если у вас воткнут какой нибудь звуковой сервер (pulseaudio, ESD, ARts...) - это не ко мне.

Зачем её вообще рассматривать ? В то время как популярные PC-шные дистрибутивы уже более-менее вылизаны по части использования
звука, как только вы выходите из уютного мирка PC на суровые поля SoC-архитектур, вы тут же тонете в болотах аппаратных
драйверов. Вы думали, что умения конфигурировать и компилировать ядро должно хватить,
а наличие представлений о device tree blob сделает вас просто бессмертным, но реальность выглядит куда страшнее.
Вы узнаёте, что такое трёхчастная модель звуковых драйверов (machine driver, platform-driver и codec-driver) и когда, наконец,
aplay сможет спеть что нибудь похожее на музыку (до этого он сперва молчал, потому что одного из слоёв драйверов не хватало,
потом хрипел, потому что без сигнала синхронизации PLL-генератор кодека всё равно что-то генерирует) и вот, выяснив, что platform-driver для
вашего SoC работает только в режиме slave на SAI-шине и codec-driver только в режиме slave (да, поэтому и не было синхронизации)...
Так вот когда вы, проявив чудеса дипломатии, заставили их всё таки работать совместно (грязно подправив исходник ядра),
вот тогда-то вы и узнаете, что проги захватывают звуковое устройство монопольно и делится не хотят. А вам как раз нужно
правый канал отдать одной программе, а левый - другой. Добро пожаловать в суровый мир ALSA-конфигов.


Термины

Минимальная управляемая сущность alsa - это плагин.

Плагин + заданные для него параметры - это устройство (IMHO).
Звукашка с драйвером тоже выглядит как устройство.

Конвеер - это цепочка соединённых друг с другом устройств.

Т.е. чтобы услышать какой нибудь писк или хруст, вам достаточно указать плееру на устройство
"hw:0,0" - первая найденная голова у первой найденной звукашки.
Но вы получите много непрязни от плейера, если: 1) Аппаратная звукашка не поддерживает требуемый
плееру семплрейт, число каналов или что нибудь ещё. 2) Если другой плеер уже что-то играет или просто
занял эту звукашку.

Чтобы это всё решить, вы указываете плейеру не hw, а default - это ссылка на цепочку (конвеер)
плагинов, которые решают все проблемы. Однако самая чёрная магия требуется, чтобы создать такую цепочку.
И ещё много разных грибов и жабьих лапок, чтобы в итоге цепочка выполняла именно то, что вы от неё хотите.


Текущее состояние ALSA

Можно увидеть многое тут : /proc/asound/
Ещё прикольно вызвать "aplay -vvv -d 1 x.wav" и тоже увидеть реально выстроенную цепочку конвеера.


Файл конфигурации ALSA

Файлов много. Но есть несколько главных, с которыми остальные связаны правилами include:

/var/lib/alsa/asound.state - конфиг микшеров для всех карт в системе.
Читается при загрузке ОС утилитой alsactl, записывается при завершении работы ОС.
alsactl запускается либо через /etc/init.d/*alsa* либо через что-то из этого:

/lib/systemd/system/alsa-utils.service
/lib/systemd/system/basic.target.wants/alsa-restore.service
/lib/systemd/system/basic.target.wants/alsa-state.service
/lib/systemd/system/alsa-restore.service
/lib/systemd/system/alsa-state.service
/lib/udev/rules.d/90-alsa-restore.rules

/var/run/lock/asound.state.lock - lock-файл для предыдущего файла.

/var/lib/alsa/asound.state.new - промежуточный файл, создаётся при записи конфиги, затем переименовывается в asound.state.
Если alsactl скулит, что ему не хватает прав для создания asound.state (хотя бы даже там уже права 0777) - на самом деле он имеет ввиду
невозможность создать asound.state.new (например, если запрещена модификация каталога /var/lib/alsa/).



/usr/share/alsa/alsa.conf - конфиг конвееров. Не только звуковых.
/etc/asound.conf, ~/.asoundrc - дополнения к конфигу конвееров, вызываются из предыдущего файла.

Кто читает этот файл ? Т.е. как применить изменения ?

Файл читается любой прогой, которая хочет работать с ALSA. Линкер, загружая прогу, цепляет библиотеку libasound.so
- вот она и читает эти файлы. Так что если вы внесли изменения в конфиг - просто перезапустите вашу программу.
Если вы грохнете файл /usr/share/alsa/alsa.conf прога не будет запускаться, зато будет прикольно валиться с ошибкой.


Пакеты, которые могут быть полезны или нужны (названия по каталогу debian):

alsa-utils		Utilities for configuring and using ALSA
    Сюда входят разные полезные утилитки, которые конфигурируют микшер (его конфигурация хранится в ядре ОС),
    а также позволяют проверить работу звуковой подсистемы.
    Ставить не обязательно, но, в случае глюков, багов и неясностей, без неё будет сложно.

libasound2:amd64	Shared library for ALSA applications
    Собственно библиотека libasound.so,
    а также полезный файл с описанием синтаксиса конфигов:
    /usr/share/doc/libasound2/examples/asoundrc.txt.gz
    Реально полезный. Но не полный.
    Там нет, как минимум:
    1) Правил перезаписи/смешивания ассоциативных массивов (но правила -то есть!).
    2) Упоминания плагина asym.

libasound2-data		Configuration files and profiles for ALSA drivers
    /usr/share/alsa
    Файлы конфигурации. Да, алсе понадобился отдельный пакет для файлов конфигураций.

libasound2-dev:amd64	Shared library for ALSA applications -- development files
    Заголовки .h
    Для разработчиков типа программистов.


Формат конфигов:

См. https://alsa-project.org/wiki/Asoundrc
См. /usr/share/doc/libasound2/examples/asoundrc.txt.gz
См. https://www.alsa-project.org/alsa-doc/alsa-lib/pages.html
См. https://wiki.archlinux.org/index.php/Advanced_Linux_Sound_Architecture

Выглядит сложным, потому что синтаксический сахар. На самом деле конструкций не так уж много:
переменная = значение
"=" можно не писать.

Разделять присвоения можно переводами строк, запятыми и ";".

Ещё есть вложения:
<имя файла>
- вставить сюда указанный файл.
<confdir:путь>
- путь по умолчанию для поиска конфигов.

/ Следует заметить, что зачитать конфиг-файл из другого конфига можно через расширение @hooks /

От знака "#" до конца строки - комментарий.
Собственно и всё. Если вам это не нравится - см. рис. 1.


Значение может быть числом.

Значение может быть булевым.

Значение может быть строковым, если строка содержит странные символы - можно взять в кавычки.
Но не обязательно. Хотя фиг знает как тогда отличить от имени переменной.
Например, если вы хотите сослаться на конкретную звукашку (контроллер), то значение
pcm="hw:0,0"
берётся в кавычки, иначе двоеточие и запятая сыграют не так, как вам бы хотелось.

Значения могут быть массивом: тогда они обрамляются "[]".

Значения могут быть ассоциативным массивом: тогда имя-значения обрамляются в "{}".

Иногда ассоциативный массив из одного элемента заменяет обычную переменную:

    slave {
        pcm "hw:0,0"
    }

то же что и

    slave.pcm "hw:0,0"

Теперь понятно, что значит вот это:
pcm.default {
  type "plug"
}
?

Это просто иная запись: pcm.default { type = plug } или pcm.default.type = plug.

Поэтому же (пример из /usr/share/doc/libasound2/examples/asoundrc.txt.gz):

    DEF.NAME1 NAME2         # DEF.NAME1 is an alias for DEF.NAME2

Ибо это тоже что и (это я так предполагаю):

    DEF { NAME1 = NAME2 }

Хотя кто его знает - может быть это означает NAME1 = "NAME2" ?

Есть ещё специальный механизм "@args", который позволяет вызвать slave с формированием нужных параметров:

pcm.demo {
    @args [ CARD DEVICE ]
    @args.CARD {
        type string
        default "supersonic"
    }
    @args.DEVICE {
        type integer
        default 0
    }
    type hw
    card $CARD
    device $DEVICE
}

Теперь его можно вызывать так (эквивалентно):
hw:0,1
hw:CARD=0,DEVICE=1
hw:{CARD 0 DEVICE 1}

А так можно ?

demo:0,1
demo:CARD=0,DEVICE=1
demo:{CARD 0 DEVICE 1}

Ну и прочие извращения:
plug:"hw:0,1"
plug:{SLAVE="hw:{CARD 0 DEV 1}"}


Что значат "!", часто употребляемые в примерах из инета ?
Для массивов pcm.!default означает, что предыдущее значение pcm.default можно забыть.
Если "!" нет - значит нужно объединить новое и предыдущее.
Есть и другие варианты: "?" - задаёт новое, если предыдущее не определено,
"+" - объединяет новое значение и предыдущее (действие по умолчанию),
"-" - изменяет существующее значение, если его нет - ругается.
Таким образом, строка, начинающаяся с !pcm, будет отличным способом выстрелить себе в ногу.
ALSA конфигурируется очень гибко.

Конфиг описывает конвейер обработки звука или чего нибудь ещё.

Конвеер состоит из элементов-обработчиков. Если в примере вам пишут "подключите плагин dmix" - это не значит, что dmix
- это такой настоящий плагин - типа как отдельный файл, который надо где-то скачать. Или модуль ядра, который загружается
через modprobe и виден через lsmod. Нифига: это просто кусок кода, вкомпиленный в asound.so. Так что не ищите его find'ом или lsmod'ом.
Обычно конвеер, с одной стороны, заканчивается ничем (и к нему подключается ваша программа), с другой стороны упирается в железку.
Однако ALSA также имеет механизм подключения и внешних плагинов (.so). Если вы их напишите, да.
Обработчиком может быть как плагин так и "устройство".
? "Устройства" можно рассматривать как обёртки вокруг плагинов. ?
? Возможно, устройства - это "этапы" конвеера (т.е. плагин + набор настроек) ?

Типичный синтаксис обработчика звукового конвеера (pcm):

pcm.ИМЯ-ЭТАПА {
  type ПЛАГИН
  ipc_key КАКОЕ-НИБУДЬ-ЧИСЛО
  ещё какие нибудь параметры - они зависят от конкретного плагина
  slave ИМЯ-ПРЕДЫДУЩЕГО-ЭТАПА
}

ИМЯ-ЭТАПА - его вы придумываете сами. Говорят, есть некие зарезервированные имена, но кроме default ничего в голову не приходит.

ПЛАГИН - plug, dmix, dsnoop, share, dshare, asym, hw, card, hooks, rate......
    plug - преобразует формат по запросу (?то, что будет видно в списке aplay -L / arecord -L?) - полезно использовать как начало конвеера.
    asym - тройник: ветвит конвеер на два продолжения (т.е. у него вместо "slave" отдельные "capture" и "playback" - продолжения).
    multi - похож на asym, но конвеер ветвится не по направлению, а по каналам (левый, правый, фронт, тыл и т.д.).
    route - Route & Volume: тасует каналы по заданной матрице уровней. Матрица содержит коэффициенты передачи каналов Client в каналы Slave.
		Заметь, что plug тоже может делать подобное.
    dmix - смешиватель нескольких потоков playback от разных программ в один поток.
    dshare - обратный к multi: позволяет использовать отдельные каналы разными ветками master.
		Похож на dmix, но легче и не умеет суммировать звук из разных программ. Работает тоже только с playback.
    share - почти тоже самое, что dshare, но требует aserver.
    dsnoop - расщепитель потока capture для нескольких разных программ.
    null - глушитель. Ну да, даже такое есть.
    softvol - тоже регулятор громкости, но совсем уж не документированный. Добавляет регулятор громкости в микшер ?
    file - будет выгружать в файл/пайп либо брать оттуда.
    hw - PCM-устройство ядра (ну то есть уже драйвер железки, обычно).
и тысячи их, включая всякие преобразователи, компрессоры, копировщики и ещё что-то.
См. /usr/share/doc/libasound2/examples/asoundrc.txt.gz
См. https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html
Набор остальных параметров зависит от типа плагина.

КАКОЕ-НИБУДЬ-ЧИСЛО - тоже придумайте сами, лишь бы было уникальным. Оно нужно для того, чтобы два разных процесса могли общаться относительно
совместного использования данного элемента конвеера. Нужно это не всем элементам. Например, модуль dmix как раз
и нужен, чтобы несколько прог могли использовать одну звукашку. Так что договариваться приходится. Так как всё крутиться в пользовательском
пространстве, то возможность договориться реализована через IPC. А чтобы было ещё веселее, к IPC можно применить традиционные права rwx
(ipc_perm, ipc_gid и ipc_key_add_uid) и получить презабавные эффекты.

ИМЯ-ПРЕДЫДУЩЕГО-ЭТАПА - куда скинуть результат дальше, если конвеер работает на вывод звука. Или от кого получать, если конвеер вводит звук.
Итоговым элементом конвеера можно задать собственно аппаратную звукашку, хотя, если глянуть /usr/share/doc/libasound2/examples/asoundrc.txt.gz
есть предположение, что можно заслать данные даже в сеть или в файл. Сослаться на аппаратную звукашку можно примерно так: pcm "hw:0,0".
Ссылаясь на предыдущий этап можно указать всякие интересные параметры (через ассоциативный массив):
    period_time 0	period - это набор семплов, обрабатываемый за одно аппаратное прерывание. На прикладном уровне не очень интересен.
    period_size 1024	size - объём в семлпах, time - объём в мкс.
    buffer_size 8192	buffer - это набор period'ов, которые могут быть обработаны подряд,
			без участия вашей программы. В общем-то это буфер, в который можно накидать и дрыхнуть. Или дрыхнуть, пока он заполняется.
    rate 44100
    channels 2
    pcm "hw:0,0"	вышеупомянуто

В программе вы ссылаетесь на звуковое устройство - им может быть любой из элементов конвеера, вы указываете на него через ИМЯ-ЭТАПА.
Всем известное ИМЯ-ЭТАПА - default.

Элемент конвеера может быть как двунаправленный (plug, например) так и однонаправленный (dmix, dsnoop...).
Плагин dmix имеет только playback, поэтому его slave должен уметь, как минимум, playback. Если slave умеет и capture - не проблема.
Плагин plug имеет playback и capture, поэтому напрямую dmix не может быть его slave'ом. Нужно использовать плагин asym между ними.
Но можно оставить только dmix и dsnoop концом конвеера, однако, в, таком случае, они будут разными устройствами и вам
придётся объяснять программе, что для playback у вас одно устройство, а для capture - другое.
И для mixer третье (и это не все программы смогут понять).

Какие конвееры ещё бывают, помимо pcm:
ctr - микшер
и другие - тысячи их: pcm_type, pcm_scope_type, pcm_scope, pcm_slave... и даже server. Конвееров как раз столько, сколько нужно.
Если вы думаете, что конвееров слишком мало или слишком много - см. рис. 1.
У каждого конвеера свои плагины. Конвеер pcm_slave часто необходимо использовать как обёртку для драйвера карты, хз почему
(т.е. нельзя сослаться на железку, например, как слейв для dshare - только через промежуточный pcm_slave).


На этом безумие гибкости ALSA не останавливается и состояния бельевой верёвки достигает в таком примере:

aplay -Dplug:\'dmix:SLAVE=\"hw:1,0\",RATE=44100\' x.wav

т.е. имя устройства - это не строка-идентификатор, как многие думают (как ещё вчера думал и я), а параметр, представляющий собой
- только в самом скучном случае - имя элемента в массиве pcm. На самом деле имя устройства - это такое же выражение,
которое можно написать в файле конфигурации.

/ Конечно, ALSA всё равно не сможет спихнуть sendmail с пьедестала по вывертам в конфиге. Но она уже обогнала squid по суммарному размеру файлов настроки /


Перехватить поток звука, варианты подумать и сконфигурировать:
- Виртуальная звуковуха-лупбэк: modprobe snd-aloop.
- Плагин pcm.type file: но файл только raw формата (впрочем, файлом может быть пайп,
  а применив две копии плагина можно завернуть поток из playback в capture)
- Плагин pcm.shm + aserver

Варианты подумать и запрограммировать:
- Плагин pcm.ioplug + собственная прога.
- Можно написать собственный плагин в отдельной .so.


Сервер aserver
Осторожное теоретическое предположение (документов на него нет даже в исходниках, вопросов/ответов в инете тоже почти нет):
Так как asound - библиотека userland'a, для работы с несколькими приложениями ей нужно иметь возможность объединять данные
для немонопольного использования звукашки, т.е. координировать работу собственных копий кода, слинкованных с разными программами.
Для этого обычно используется разделяемая память (IPC). Но существует и другой механизм: aserver. Например, плагин pcm.dshare работает
через IPC, в то время как аналогичный по функционалу pcm.share - через aserver. Через IPC также работают dmix и dsnoop. Кто работает
через aserver кроме share - науке не известно. Нужен ли для pcm.share pcm.shm или же pcm.shm делает что-то обособленно-полезное также неизвестно.

Вероятно, для систем, где доступна IPC (shared memory) - это более быстрый вариант, чем aserver. Но aserver более универсальный (портируемый).

Буква 'd' в названиях dmix, dsnoop, dshare происходит от слова direct - т.е. прямой доступ к общему pcm-буферу.


Вопросы:
1) какие же из этапов конвеера видны по командам aplay -L / arecord -L ?
2) в чём разница между default и sysdefault ? почему sysdefault может быть не виден в списке, но доступен программам ?
3) что такое aserver.c, какая в нём польза, как использовать ? Связаны ли с ним плагин shm и конвеер server ?
4) плагин pcm.meter и level.c. Для чего нужен level.c ? Как это использовать ?
5) pcm.share vs pcm.dshare vs pcm.multi ? в чём разница ?
6) pcm.softvol - что же он делает или зачем ему min/max volume ?
7) dshare vs dmix ?
    dmix суммирует звук от всех программ и выдаёт "итого". Каждый канал обрабатывает поотдельности.
    dshare не суммирует звук, но позволяет каждый канал отдать отдельной программе, не получив при этом resource busy.
    Как уверяет лично автор в alsa-devel@lists.sourceforge.net: если используется только одна программа на один канал,
    то dmix аналогичен по эффекту dshare. Но dshare более программно-эффективен, так что не стоит без нужды использовать dmix.

-=-

Как изобразить две виртуальные одноканальные карты из двух каналов физической карты ?
(Чтобы два разных приложения могли читать и писать свой канал. Микшер не трогаем)

pcm.!default {
}

pcm_slave.hwcard {
	pcm "hw:0,0"
#	channels 8
#	period_time 40000
#	buffer_time 360000
#	format S16_LE
#	rate 32000
}

pcm.le {
	type plug
	slave.pcm {
		type asym
		playback.pcm {
			type dshare
			ipc_key 87654
			slave hwcard
			bindings [ 0 ]
		}
		capture.pcm {
			type dsnoop
			ipc_key 43210
			slave hwcard
			bindings [ 0 ]
		}
	}
}

pcm.ri {
	type plug
	slave.pcm {
		type asym
		playback.pcm {
			type dshare
			ipc_key 87654
			slave hwcard
			bindings [ 1 ]
		}
		capture.pcm {
			type dsnoop
			ipc_key 43210
			slave hwcard
			bindings [ 1 ]
		}
	}
}

ctl.mixer0 {
	type hw
	card 0
}

Теперь aplay -D le будет работать с левым каналом (думая, что у нас моно- карточка),
а arecord -D ri - с правым каналом.

arecord можно запустить несколько копий, а aplay - только одну копию на канал.

Владимир