Как я осовременивал двадцатилетний домашний кинотеатр, или как упасть в глазах аудиофилов
- среда, 4 декабря 2024 г. в 00:00:09
Привет, Хабр! Мне вот сказали, что было бы интересно почитать тут статью о моих приключениях с доставшимся мне по наследству Philips LX8300SA, как я его пытался осовременить для использования с моим Mac. Поэтому я и решил попробовать себя в написании своей первой тут статьи :)
Отрыл у нас в квартире эту вундервафлю, попытался подключить - динамики показались вполне неплохими. Быстренько собрал полную конфигурацию у себя на столе, звук понравился больше, чем у недавно проданной тогда Яндекс Станции 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). Фото задних динамиков не будет, простите. Мне слишком стыдно показывать колхоз, на котором они держатся. Передние динамики немного повернуты, чтобы быть более направленными в сторону слушателя.
Самая интересная часть - как же передавать многоканальный звук на данный AV ресивер с современного железа? Для начала - аналоговых входов на каждый канал у него нет.
Да и 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 правильно раскидывал звук на динамики?
С первого взгляда нетривиальная задача. Давайте посмотрим найденных мною кандидатов, а именно способы стриминга между платформами в целом:
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 система).
Спасибо за ваше внимание, буду рад услышать критику, так или иначе, моя первая статья тут :)