Как я осовременивал двадцатилетний домашний кинотеатр, или как упасть в глазах аудиофилов

от автора

Привет, Хабр! Мне вот сказали, что было бы интересно почитать тут статью о моих приключениях с доставшимся мне по наследству Philips LX8300SA, как я его пытался осовременить для использования с моим Mac. Поэтому я и решил попробовать себя в написании своей первой тут статьи 🙂

Главный герой этой статьи, Philips LX8300SA

Главный герой этой статьи, Philips LX8300SA

Как все начиналось

Отрыл у нас в квартире эту вундервафлю, попытался подключить — динамики показались вполне неплохими. Быстренько собрал полную конфигурацию у себя на столе, звук понравился больше, чем у недавно проданной тогда Яндекс Станции 2. К сожалению, фото уже не найду, но расстановка 5.1 там была нарушена полностью. Сделав AirPlay через ShairPort-Sync, слушал на нем треки из Apple Music. А потом мне захотелось большего — наконец понять, правда ли треки в Dolby Atmos так хороши.

Скрытый текст

Я прекрасно знаю, что 5.1(.0) система недостаточна для Dolby Atmos. Но, даже в документации Dolby по конфигурациям аудиосистем, есть отступление, что в системах без потолочных/up-firing динамиков можно использовать «виртуальный» Atmos.

Up-firing динамики — это колонны, так же играющие звук в потолок под определенным углом. Ими можно отчасти заменить потолочные динамики в конфигурациях Dolby Atmos.

Расстановка динамиков

Как я уже написал выше, изначально все это добро просто стояло на столе. Никакой схемы расстановки, upmix делал Dolby Pro Logic II, встроенный в DSP домашнего кинотеатра. В общем, дикий колхоз и 0 смысла от 5.1 конфигурации. Ниже показана авторская репрезентация того, как это было.

Мама, я - графический дизайнер!

Мама, я — графический дизайнер!

После того, как я захотел испытать на слух нормальный 5.1, я сделал нормальную расстановку по гайдам Dolby. Задние динамики разнесены по краям комнаты (RL — с края дивана, RR — на коробке от штатива у стенки), стол стоит по центру комнаты (стены комнаты), напротив дивана, на его краях FL и FR динамики, а по центру стола — сабвуфер (LFE). Фото задних динамиков не будет, простите. Мне слишком стыдно показывать колхоз, на котором они держатся. Передние динамики немного повернуты, чтобы быть более направленными в сторону слушателя.

Расстановка динамиков на столе

Расстановка динамиков на столе

Передача 6-канального звука на двадцатилетний домашний кинотеатр с современной машины

Самая интересная часть — как же передавать многоканальный звук на данный AV ресивер с современного железа? Для начала — аналоговых входов на каждый канал у него нет.

Порты LX8300SA

Порты LX8300SA

Да и ALC662 на борту моего Linux сервера — довольно посредственный кодек. Поэтому вариант с передачей аналогового звука я отбросил сразу же. Остается только коаксиальный S/PDIF. Какие же кодеки мы имеем на борту?

  • PCM 2.0 (заявлены 48kHz/24bit, но, если верить ALSA, спокойно принимает и 96kHz/32bit)

  • Dolby Digital (AC-3) 5.1 до 640 Kbps

  • DTS 5.1 до 768 Kbps

Скрытый текст

Потолок у Dolby AC-3 по битрейту — 640 Kbps даже на бумаге, так что тут с моими опытами все ясно.

Что же не так с DTS? По умолчанию, DTS на DVD шел с битрейтом в 1536 Kbps. Это позволяло получить довольно приличное качество для 6-канального lossy кодека. Пропатчив dcaenc (прозрачный энкодер DTS для ALSA), опытным путем выяснил, что максимальный битрейт без артефактов — 768Kbps. Дело, скорее всего, не в подключении. Битрейт того же PCM 2.0 48kHz / 24 bit уже равен 48 kHz * 24 bit * 2 ch = 2304 Kbps. И он работает нормально.

Самый простой вариант применить эти кодеки, который я нашел — использовать Linux сервер для прозрачного энкодинга PCM 5.1 в Dolby AC-3 / DTS. Я использую Fedora, установка плагина ALSA для AC-3 была довольно простой:

sudo dnf install alsa-plugins-a52

Далее я поднял битрейт в стандартном конфиге A52 по пути /etc/alsa/conf.d/60-a52-encoder.conf:

... @args.BITRATE {   type integer   default 640 } ...

Качество звука при проигрывании тестовых файлов через mpv меня устраивало. С DTS оно было хуже, поэтому я остановился на Dolby AC-3. Но теперь возник вопрос: как передать 5.1 звук с Mac на Linux, причем так, чтобы Mac видел это безобразие как 5.1 звуковое устройство, чтобы декодер Atmos в Apple Music правильно раскидывал звук на динамики?

Стриминг 5.1 звука по сети с Mac на Linux

С первого взгляда нетривиальная задача. Давайте посмотрим найденных мною кандидатов, а именно способы стриминга между платформами в целом:

  • NetJACK2. Хороший вариант, если бы сервер был у меня в LAN. К сожалению, весь трафик роутера идет через сервер, он стоит в WAN, поэтому мультикаст добраться до локальной сети не сможет.

  • ROC: Новый способ передачи звука по сети, имеет удобные утилиты и низкую задержку. Но не поддерживает 5.1 звук (есть незакрытый Issue с полупустым чеклистом на момент написания статьи).

  • AirPlay: Не поддерживает Dolby Atmos стриминг (хоть и есть движения в этом направлении).

Задуманные для этого добра методы нам не подходят. Итак, пришло время делать костыли — за протокол был взят PulseAudio, который умеет прозрачно передавать звук по TCP. Для включения этого протокола на PipeWire требуется создать файл ~/.config/pipewire/pipewire-pulse.conf.d/network.conf :

pulse.properties = {     server.address = [         "unix:native"         "tcp:192.168.128.1:4656"     ] }

192.168.128.1 здесь — локальный IP самого сервера.

Теперь поговорим о стороне клиента. Устанавливаем PulseAudio на macOS (да, я сам был удивлен, что это — кроссплатформа):

brew install pulseaudio

Но тут возникает проблема. PulseAudio на macOS не умеет работать с CoreAudio (подсистемой аудио на Mac), в результате чего устройств из PA мы не видим. Поэтому я пошел пересобирать BlackHole (Open-Source loopback драйвер под Mac). Устанавливаем Xcode Command Line Tools, скачиваем репозиторий и собираем с изменненой конфигурацией (названия изменены для отсутствия конфликтов с обычным BlackHole) без codesign:

xcodebuild CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO \   -project BlackHole.xcodeproj \   GCC_PREPROCESSOR_DEFINITIONS='$GCC_PREPROCESSOR_DEFINITIONS   kLatency_Frame_Size=4096 kNumber_Of_Channels=6 kSampleRates=44100,48000   kDevice_IsHidden=false kDevice_HasInput=true kDevice_HasOutput=false   kDevice_Name="PhilipsCapture"   kDevice2_Name="Philips5.1"   kDevice2_IsHidden=false kDevice2_HasInput=false kDevice2_HasOutput=true'

Далее подписываем драйвер для использования локально на вашем Mac, чтобы Gatekeeper не делал нам мозг:

sudo codesign --force --deep --sign - build/Release/BlackHole.driver

Следующий вопрос — чем писать звук в pipe? Самым стабильным вариантом у меня оказался sox. Устанавливаем:

brew install sox

После этого я решил на скорую руку сделать скрипт для быстрого включения всего этого безобразия. Пройдемся по основным частям.

Аргументы запуска. Из основных для себя настроек я выделил звуковую схему (5.1 / 2.0) и тип моего расположения в данный момент (диван/стол). О расположении поясню далее.

#!/bin/bash  # Sound stage mode: # 5.1 / 2.0 MODE="$1" [ -z "$MODE" ] && MODE="5.1"  # 5.1 mode: # table / couch MODE51="$2" [ -z "$MODE51" ] && MODE51="couch"  # SSH target TARGET="tdrk@supercomputer.local"

Адаптация громкости динамиков под моё расположение. Иногда я люблю слушать музыку за столом, сидя на кресле, а иногда сидя на диване. Понятное дело, что расстояния между мною и передними / задними динамиками при смене моей позиции сильно меняются. Это чинится с помощью громкости и задержки на ближайших к слушателю динамиках (так уж вышло, что скорость звука не такая и высокая). Задержку я так и не понял как реализовать на PipeWire, так что ее я пропустил. Подобрал вот такие значения опытным путем:

51prepare() {     # 0 - 65535     if [ "$MODE51" = "table" ]; then         VOL_FS=53768         VOL_FC=50368         VOL_RS=65535         VOL_LFE=65535     elif [ "$MODE51" = "couch" ]; then         VOL_FS=65535         VOL_FC=65535         VOL_RS=60535         VOL_LFE=65535     else         echo "Unknown 5.1 mode - $MODE51"         exit 1     fi      # FL FR RL RR FC LFE     ssh "$TARGET" "pactl set-sink-volume $PADEVICE $VOL_FS $VOL_FS $VOL_RS $VOL_RS $VOL_FC $VOL_LFE"  }

Подготовка клиента и сервера к проигрыванию. Необходимо выбрать нужный профиль S/PDIF и нужное звуковое устройство на Mac. Для этого надо скачать еще одну маленькую утилиту:

brew install switchaudio-osx

В коде все выглядит так:

setprofile() {     ALSA_PCI="pci-0000_00_1b.0"      ssh "$TARGET" "pactl set-card-profile alsa_card.$ALSA_PCI output:$1"     export PADEVICE="alsa_output.$ALSA_PCI.$1"     sleep 0.2 # Let AV receiver detect Dolby Digital / PCM output }  prepare() {     if [ "$MODE" = "2.0" ]; then         setprofile "iec958-stereo"         DEVICE="BlackHole 2ch"     elif [ "$MODE" = "5.1" ]; then         setprofile "iec958-ac3-surround-51"         DEVICE="Philips5.1"         51prepare # Adjust volumes depending on listener position     else         echo "Unknown sound stage $MODE"         exit 1     fi     export PADEVICE     SwitchAudioSource -s "$DEVICE" }

Стриминг. Sox пишет данные из CoreAudio, отправляя их в pipe в формате PCM 5.1 (48kHz, S32LE). В аргументы функции пишем Loopback устройство на вход и маппинг каналов в формате PulseAudio.

run() {     PASERVER="192.168.128.1:4656"     CAP_DEVICE="$1"     CHANNEL_MAP="$2"      # Space acts as a +1     CHANNELS=$(printf '%s ' `sed 's/[^,]//g' <<< "$CHANNEL_MAP"` | wc -c)     sox \         -r 48000 -b 32 -c $CHANNELS -e signed-integer \         -t coreaudio "$CAP_DEVICE" \         -p | paplay \             -s "$PASERVER" -d "$PADEVICE" -v --raw \             --rate 48000 --format s32le --channels $CHANNELS \             --channel-map="$CHANNEL_MAP" }

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

pick() {     case "$MODE" in     "5.1") run \         "PhilipsCapture" \         "front-left,front-right,front-center,lfe,rear-left,rear-right"     ;;     "2.0") run \         "BlackHole 2ch" \         "front-left,front-right"     ;;     *) echo "Unknown sound stage $MODE"        exit 1     ;;      esac }

Заходим в Audio MIDI Setup и выставляем каналы и частоту дискретизации как указаны ниже.

Скрытый текст

48000 Гц

1 — передний левый
2 — передний правый
3 — передний центральный
4 — сабвуфер
5 — задний левый
6 — задний правый

Заканчивается скрипт простым while true для автоматического перезапуска всей системы при обрывах соединения:

prepare while true; do pick sleep 0.1 done

Полный текст скрипта:

Скрытый текст
#!/bin/bash  # Sound stage mode: # 5.1 / 2.0 MODE="$1" [ -z "$MODE" ] && MODE="5.1"  # 5.1 mode: # table / couch MODE51="$2" [ -z "$MODE51" ] && MODE51="couch"  # SSH target TARGET="tdrk@supercomputer.local"  51prepare() {     # 0 - 65535     if [ "$MODE51" = "table" ]; then         VOL_FS=53768         VOL_FC=50368         VOL_RS=65535         VOL_LFE=65535     elif [ "$MODE51" = "couch" ]; then         VOL_FS=65535         VOL_FC=65535         VOL_RS=60535         VOL_LFE=65535     else         echo "Unknown 5.1 mode - $MODE51"         exit 1     fi      # FL FR RL RR FC LFE     ssh "$TARGET" "pactl set-sink-volume $PADEVICE $VOL_FS $VOL_FS $VOL_RS $VOL_RS $VOL_FC $VOL_LFE"  }  setprofile() {     ALSA_PCI="pci-0000_00_1b.0"      ssh "$TARGET" "pactl set-card-profile alsa_card.$ALSA_PCI output:$1"     export PADEVICE="alsa_output.$ALSA_PCI.$1"     sleep 0.2 # Let AV receiver detect Dolby Digital / PCM output }  prepare() {     if [ "$MODE" = "2.0" ]; then         setprofile "iec958-stereo"         DEVICE="BlackHole 2ch"     elif [ "$MODE" = "5.1" ]; then         setprofile "iec958-ac3-surround-51"         DEVICE="Philips5.1"         51prepare # Adjust volumes depending on listener position     else         echo "Unknown sound stage $MODE"         exit 1     fi     export PADEVICE     SwitchAudioSource -s "$DEVICE" }  run() {     CAP_DEVICE="$1"     CHANNEL_MAP="$2"      # Space acts as a +1     CHANNELS=$(printf '%s ' `sed 's/[^,]//g' <<< "$CHANNEL_MAP"` | wc -c)     sox \         -r 48000 -b 32 -c $CHANNELS -e signed-integer \         -t coreaudio "$CAP_DEVICE" \         -p | paplay \             -s 192.168.128.1:4656 -d "$PADEVICE" -v --raw \             --rate 48000 --format s32le --channels $CHANNELS \             --channel-map="$CHANNEL_MAP" }  pick() {     case "$MODE" in     "5.1") run \         "PhilipsCapture" \         "front-left,front-right,front-center,lfe,rear-left,rear-right"     ;;     "2.0") run \         "BlackHole 2ch" \         "front-left,front-right"     ;;     *) echo "Unknown sound stage $MODE"        exit 1     ;;      esac }  prepare while true; do pick sleep 0.1 done 

./audio.sh 5.1 couch

И… Оно работает! Не без проблем, но работает. И, за свои 0 рублей (кинотеатр этот достался мне по наследству), звук просто шикарен. Что еще нужно для счастья?)

Заключение

Иногда соединение прерывается, иногда приходится делать systemctl --user restart pipewire pipewire-pulse (так и не разобрался, почему), но я доволен результатом такого колхоза и костылей, даже такой 5.1 звук имеет очень выраженный объем, многие треки из своей библиотеки я будто бы первый раз слушал.

Скрипт не идеален, потому-что писал в основном для себя. Мне будет приятно, если эта статья была вам чем-то полезна или просто принесла удовольствие при прочтении!)

Если таки найду деньги на покупку второго такого же кинотеатра и USB S/PDIF, попытаюсь выпустить вторую часть с доработкой данной 5.1 системы до 5.2.4 (уже Atmos-enabled система).

Спасибо за ваше внимание, буду рад услышать критику, так или иначе, моя первая статья тут 🙂


ссылка на оригинал статьи https://habr.com/ru/articles/861748/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *