Глава 1. Bluetooth
Захотелось на днях собрать bluetooth-адаптер для удобной передачи аудио-потока с мобильника на стереосистему. В основном ради аудиокниг для ребёнка, но не только (стоило сделать до каникул, ага).
Готовую bluetooth-плату с Али брать не хотелось, т. к. 99% - шлак в плане качества звука. Есть варианты с i2s-выходом, но у меня сейчас нет под рукой ЦАПа, к которому можно подключить i2s без колхоза. Зато есть USB-шный.
Посему решил попробовать собрать то же самое на каком-нить недорогом одноплатнике с Linux. В качестве бонуса есть желаение на нём же поднять DLNA-renderer для полноценного беспотерьного аудио, но это в следующей серии (похоже геморроя будет, как ни странно, больше).
Кратко опишу процесс, может поможет кому-нибудь. А может и мне самому через годик-другой
0. Платку взял OrangePi zero2. Причины: недорогая, достаточная производительная (4 ядра Cortex-A53), достаточно ОЗУ (1ГБ), на борту есть необходимые мне интерфейсы: WiFi5/BT5 (есть даже надежда на нормальный coexistence), 1Gb ethernet, USB2-host (один нормальный, ещё пара на гребёнке), serial console, кучка GPIO. Хватит в т. ч. на некоторую перспективу.
1. Попробовал избежать китайских образов и поставить православный Armbian. С ходу не взлетел WiFi, и нестабильно определялся установленный объём ОЗУ. Разбираться было лень, так что вернулся на штатный китайсктй образ. Пошёл по пути наименьшего сопротивления и залил самый свежий из доступных у производителя вариант Ubuntu server, на сегодня это jammy с ядром 5.16.17.
2. Подключился через serial console через переходничок USB-UART: под виндой вроде можно через putty, либо hyperterminal (если кто-то такое ещё помнит). Настроил wifi: из orangepi-config в моей терминальной программе было жутко неудобно, поэтому сделал через nmcli.
Дальше вся работа шла по ssh.
Если удобно подключить плату через ethernet, то можно обойтись без serial console и WiFi.
3.
Код:
sudo apt update && sudo apt upgrade
Если этого не сделать, то со старой версией bluez всё будет несколько иначе.
4. (Написано неразборчивым почерком) ...что-то понажимал в утилитах настройки bluetooth, чтобы устройство было всегда pairable и discoverable, то ли в bluetoothctl, то ли ещё где.
5. Нужно добиться спаривания внешних устройств с нашей апельсинкой. За авторизацию (пин коды и вот это всё) отвечают т. н. bluetooth agent*ы. Тот, что встроен в bluez, даже если сказать ему NoInputNoOutput, даёт спариться, но начинает сыпать в консоль вопросы при подключении A2DP-сервиса. Отвечать на эти вопросы будет некому, так что не годится.
Ставим bluez-tools (sudo apt install bluez-tools) и используем его агент. Для этого пришлось сделать простой systemd-сервис, а именно создать в /usr/lib/systemd/system/ файлик bt-agent.service следующего содержания:
Код:
[Unit]
Description=bt-agent unattended service
After=dbus-org.bluez.service
Requires=dbus-org.bluez.service
[Service]
Type=simple
ExecStart=/usr/bin/bt-agent -c NoInputNoOutput
Restart=on-failure
KillSignal=SIGKILL
[Install]
WantedBy=bluetooth.target
Теперь вызываем
Код:
sudo systemctl daemon-reload
sudo systemctl enable bt-agent.service
У bt-agent есть ещё ключик -d (daemonize), но с ним у меня почему-то не взлетало.
6. Теперь нужно установить нечто, что будет принимать поток из bluez, декодировать его и отправлять в alsa-устройство. Это нечто называется bluealsa (она же bluez-alsa). Но штатная в Убунте не умеет в aptX, AAC и тому подобные модные кодеки, только унылый SBC. Искать готовые сборки - не спортивно, так что идём во все тяжкие...
Код:
sudo apt install git libglib2.0-dev libdbus-1-dev automake build-essential libtool pkg-config python3-docutils autotools-dev libtool pkg-config gcc binutils libasound2-dev libbluetooth-dev libopenaptx-dev libsbc-dev libfdk-aac-dev libmp3lame-dev
Последние четыре пакета опциональны, но с конфигурацией сборки, которая будет ниже, они нужны.
Вроде ничего не забыл.
Собираем и устанавливаем...
Код:
git clone https://github.com/Arkq/bluez-alsa.git
cd bluez-alsa
autoreconf --install --force
mkdir build
cd build
../configure --with-libopenaptx --enable-aptx --enable-aptx-hd --enable-aac --enable-mp3lame --enable-systemd
make
sudo make install
7. Выясняем имя alsa-устройства, в которое будем выводить звук. Например, с помощью команды aplay -L
У меня это plughw:CARD=Audio,DEV=0
Оно нам потребуется на следующем шаге.
Префикс plughw в этом автоматически созданном виртуальном устройстве означает, что будет использован плагин Alsa "plug" для автоматического преобразования формата потока.
У меня без этого плагина не взлетало по причине несовместимости ЦАПа с битностью потока (не принимает 16 бит).
Если нужна передискретизация, то вроде надо ещё что-то подкрутить, т. к. по дефолту ресемплер хуже некуда. Мне не надо, поэтому не копал.
8. Подправляем файлик /usr/lib/systemd/system/bluealsa-aplay.service , а именно ищем там строку, начинающуюся с ExecStart=/usr/bin/bluealsa-aplay и меняем её на
Код:
ExecStart=/usr/bin/bluealsa-aplay -S -D "plughw:CARD=Audio,DEV=0"
Имя устройства, естественно, подставьте своё.
9. Подправляем файлик /usr/lib/systemd/system/bluealsa.service , а именно ищем там строку, начинающуюся с ExecStart=/usr/bin/bluealsa и меняем её на
Код:
ExecStart=/usr/bin/bluealsa -S -p a2dp-sink --codec=aptX --codec=aptX-HD
То есть оставляем только a2dp-приёмник.
10. Выполняем
Код:
sudo systemctl daemon-reload
sudo systemctl enable bluealsa.service
sudo systemctl enable bluealsa-aplay.service
Возможно, делать enable и не требуется, но не помешает.
11. Вроде всё.
sudo reboot now
И должно взлететь если до этого не было ошибок.
Мой дешманский китайфон успешно транслирует звук в AAC.
Инструментальную оценку качества пока не делал, а на слух моя текущая система не позволит что-либо оценить. Да и поздно уже
Имейте ввиду, безопасности никакой!
Кто угодно может в любой момент подключиться к настроенному таким образом блютус-устройству и передать на него смертельный зомбирующий убивающий пищалку сигнал.
Глава 2. Микширование и ресемплинг в Alsa
Раз уш надумал прикрутить ещё и DLNA, значит придётся микшировать несколько потоков от разных устройств. Конечно, можно решить вопрос административно и запрещать родным и гостям подключаться одновременно. Но всё равно будут случаи когда кто-то нажмёт на паузу, а плеер продолжит транслировать тишину. Я недостаточно деспотичен, чтобы наказывать за такие проступки
Да и в рамках единственного Bluetooth-интерфейса подобный ситуации возможны. Значит делаем.
Как это должно работать:
- Каждый звуковой поток нужно передискретизировать в одну и ту же частоту (любую, но одинаковую).
- Все потоки микшируем (при равных частотах дискретизации это просто сложение семплов) и выводим на нужное звуковое устройство.
Обойтись без передискретизации можно, но во-первых, сложнее, а во-вторых, будут ограничения в удобстве. Я так не хочу.
1. Выбираем подходящую частоту дискретизации. У меня 99% материала (который я хочу воспроизводить качественно) записан с частотой 44.1кГц. Но я сторонник удвоения частоты дискретизации (первая ступень цифровой фильтрации) на ПК, поэтому ставлю 88.2кГц. Впрочем, немногочисленный материал в 96 и 192кГц вполне неплохо (на слух) передискретизируется в 88.2к, поэтому тоже норм.
2. Создаём файл /etc/asound.conf и прописываем туда следующее:
Код:
pcm.dmix_usb88k2 {
type dmix
ipc_key 6734782 # any unique value
ipc_key_add_uid false
ipc_perm 0666
slave {
pcm "hw:CARD=Audio,DEV=0"
format S32_LE
rate 88200
channels 2
}
}
pcm.usb88k2 {
type plug
slave {
pcm "dmix_usb88k2"
rate 88200
}
rate_converter "speexrate_medium"
hint {
show on
description "Out to Sonata USB with dmix and decent SRC to 88k2"
}
}
Синтаксис конфигов Alsa совершенно наркоманский, поэтому немного разберу.
- Создаём виртуальное устройство dmix_usb88k2, микширующее сигналы (type dmix). Присваиваем уникальный ID, делаем его одинаковым для всех пользователей и даём доступ всем пользователям к соответствующему объекту в ОЗУ. Вывод результата микширования производится в устройство hw:CARD=Audio,DEV=0 ("hw.", то есть напрямую в железку без каких-либо преобразований). Устройство поддерживает разные форматы семплов и частоты дискретизации, поэтому явно их указываю. У вас, конечно, устройство будет другое. Метод передискретизации - "speexrate_medium", работает на слух нормально. Загрузка процессора что-то около 12% одного ядра. Можно поставить "speexrate_best" (лучше в Alsa вроде нет), но тогда загрузка будет великовата (60+), поэтому остался на этом.
- Дальше создаю второе виртуальное устройство usb88k2, осуществляющее при необходимости передискретизацию (plug). Передискретизируем в 88.2кГц и выводим результат в микшер (устройство созданное перед этим).
3. Прописываем устройство usb88k2 в файл /usr/lib/systemd/system/bluealsa-aplay.service.
4. Выполняем sudo systemctl daemon-reload, перезагружаемся.
Глава 3. DLNA-renderer
Не знаю, каким местом я гуглил в прошлый раз, но сейчас с ходу нашёлся расово верный опенсорсный DLNA-renderer - gmediarender.
1. sudo apt install gstreamer1.0-alsa gstreamer1.0-plugins-base gstreamer1.0-plugins-good gmediarender
2. Вариант со штатным запуском через init.d у меня не взлетел (судя по всему, из за долгой инициализации WiFi-сети). В тонкой настройке init.d я не копенгаген, поэтому сделал systemd-службу на основе конфига от автора. Штатные конфиги для init.d не мешают, т. к. при таком же имени службы systemd имеет бОльший приоритет. Создаём файл /usr/lib/systemd/system/gmediarender.service и пишем в него следующее:
Код:
[Unit]
Description=gmrender-resurrect service
After=network-online.target sound.target
StartLimitIntervalSec=20
StartLimitBurst=50
[Service]
Environment="UPNP_DEVICE_NAME=MyBestInTheUniverseDlnaRenderer"
ExecStartPre=/bin/sh -c "/bin/systemctl set-environment UPNP_UUID=`ip link show | awk */ether/ {print \"salt:)-\" $2}* | head -1 | md5sum | awk *{print $1}*`"
ExecStartPre=/bin/sh -c "ip r | grep default"
ExecStart=/usr/bin/gmediarender -f "$UPNP_DEVICE_NAME" -u "$UPNP_UUID" \
--gstout-audiosink=alsasink --gstout-audiodevice=usb88k2 \
--logfile=/tmp/gmediarenderer.log --gstout-initial-volume-db=0
Restart=always
[Install]
WantedBy=multi-user.target
После UPNP_DEVICE_NAME= можно указать своё имя DLNA-renderer*а, а после --gstout-audiodevice= - имя alsa-устройства.
Строку --logfile=/tmp/gmediarenderer.log можно убрать. Но если ОЗУ в избытке, то можно и оставить.
Строка ExecStartPre=/bin/sh -c "ip r | grep default" выкидывает ошибку если в системе еще нет шлюза по умолчанию (обычно это означает, что локалка не работает). Без этого грязного хака не получалось заставить стабильно работать DLNA с единственным WiFi-подключением.
3. Выполняем
Код:
sudo systemctl enable gmediarender.service
sudo systemctl start gmediarender.service
Всё. Перезагружаемся и проверяем.
Теперь можно использовать наш одноплатник как полноценную часть DLNA-системы (с блекджеком, медиасервером и контроллером).
Если вы используете Линукс на ПК, то можно и просто транслировать на нашу платку звуковой поток. Для этого ставим на ПК пакет pulseaudio-dlna и запускаем его, например так: pulseaudio-dlna --codec=flac (последний аргумент говорит, что кодировать звук надо не в mp3, а во flac). Он сам найдёт в сети все renderer*ы и создаст для каждого из них pulseaudio-устройство. Когда начнёте выводить в него звук - запустится воспроизведение звука на одноплатнике (в "простое" Alsa-устройство на нём не будет занято).
Социальные закладки