Опубликовано 11 сентября 2016, 19:10 под ABSRDST - Mom

Гуляя по парку возле префектуры ЗАО, я вспоминал тот солнечный апрельский день, когда сделал свою первую музыку в ванной. Перед глазами проплывали коробки яблочного сока J7 с балкона, лабы по МО и магазин «электронщик» в шаговой доступности. Всё это приятно контрастировало с пасмурной московской природой, похожей на утренний комьют из Вантаа в центр Хельсинки. Полная ортогональность и, вместе с тем, невероятная параллельность того, что было пять лет назад и того, что есть сейчас, радовала душу и наполняла смыслом почти что часовой поход вдоль линии метро. Но эти воспоминания оказались ложью — пост датируется июлем, а контекст уложенной на холодильник колонки SVEN состоит из велосипеда STELS, упавшего сервера на Юго-Западном и, через несколько часов, неожиданно сданной сессии. Потом была вторая музыка — на всё том же велосипеде STELS в фритюрном доме, где играют Adele и Morphine; тогда хотелось сделать всё универсальным и капитальным, чтобы на века. А за ней и третья, идеальная во всех отношениях и ныне сиротливо молчащая. Прогулка подходила к своему концу: в хитро спрятанном закутке ТЦ возле метро «Молодёжная» я за 990 рублей приобрёл музыкальный Wi-Fi-приемник «Nakamichi (!) MR-01»

Nakamichi, мать его.

Те самые лучшие в мире кассетные магнитофоны.

Собственно, какой вариант был сделать музыку в ванной (а потом и на кухне) в очередной съёмной квартире? Можно по-старинке кинуть кабель. 30 метров, потому что квартира реально огромная, я не во всех комнатах каждый день бываю! По деньгам выйдет рублей 500 вместе с разъёмами, плюс часа четыре на пайку, разбор плинтусов, бурение, уборку после. Если устанем жить окнами на одну из самых оживлённых магистралей столицы — придётся делать всё заново. Нет уж, больше я этих ошибок повторять не буду. Bluetooth на такие расстояния не работает, значит, будет Wi-Fi! В качестве колонок приобрёл в «М-Видео» в соседнем доме Sven SPS-605: критерием выбора было питание от сети (95% акустики такого формата нынче питаются от USB) и размер не больше 8.5 см. (единственное место, куда их можно запихнуть в этой ванной). Получилось вот так:

Всё максимально неряшливо, потому что работает и ладно. Теперь о том, как работает.

Чтобы стримить музыку в Linux через DNLA, необходимо установить pulseaudio и pulseaudio-dlna. Последний обнаружит все устройства, поддерживающие стриминг, и создаст для них синки. Очевидно, вся эта система работает как в анекдоте. Pulseaudio на Hummingboard с Debian Jessie не может вывести звук в S/PDIF без заиканий — причём, у давно не слушанного ABSRDST в первом альбоме такая музыка, что я подумал, что он ещё и либо interleaving путает, либо вовсе фреймы в обратном направлении играет. Pulseaudio-dlna при пропадании связи с устройством никак об этом не сигнализирует; при восстановлении, естественно, ничего не восстанавливается, приходится перезапускать. После перезапуска MPD, игравший в два синка, начинает дважды играть в единственный оставшийся — звучит это весьма угрожающе. Кто этим всем пользуется я вообще не понимаю.

К счастью, у pulseaudio-dlna легко включаются отладочные логи и они довольно подробные, так что выудить оттуда механизм воспроизведения аудио-потока через DLNA не составило труда: всего 1 HTTP и два SOAP-запроса. Играет обычный HTTP-стрим в MP3, чудесный MPD умеет такой стрим выдавать, правда, всё равно пришлось сделать прокси, чтобы добавить некоторые заголовки. Nakamichi назначен статический IP-адрес, а порт у него всё время один и тот же, так что с Zeroconf можно не заморачиваться. В итоге получилось как-то так:

from http.server import HTTPServer, BaseHTTPRequestHandler
from lxml import objectify
import os
import requests
import signal
import textwrap
import threading

MY_IP = "192.168.0.3"
MY_PORT = 55426
MPD_URL = "http://192.168.0.4:8000"
CLIENT_URL = "http://192.168.0.10:55426"


class MpdProxy(BaseHTTPRequestHandler):
    def do_GET(self):
        self.wfile._sock.settimeout(10)

        for k, v in {
            "Content-Disposition": "inline;",
            "Content-Type": "audio/mpegurl",
            "Ext": "",
            "Connection": "close",
            "contentFeatures.dlna.org": "DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000",
            "transferMode.dlna.org": "Streaming",
        }.items():
            self.send_header(k, v)
        self.flush_headers()

        try:
            stream = requests.get(MPD_URL, stream=True)
            for chunk in stream.iter_content(8192, False):
                self.wfile.write(chunk)
        finally:
            os.kill(os.getpid(), signal.SIGTERM)


server = HTTPServer((MY_IP, MY_PORT), MpdProxy)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True
server_thread.start()

discovery = objectify.fromstring(requests.get(CLIENT_URL).content)
for service in discovery.device.serviceList.service:
    if str(service.serviceType).startswith("urn:schemas-upnp-org:service:AVTransport:"):
        avtransport_control_url = str(service.controlURL)
        break
else:
    raise Exception("Unable to find AVTransport control URL")

print(requests.post(
    CLIENT_URL + avtransport_control_url,
    headers={
        "Content-type": "text/xml; charset=utf-8",
        "SOAPAction": "\"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI\""
    },
    data=textwrap.dedent("""\
        <?xml version="1.0" encoding="utf-8" standalone="yes"?>
        <s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
            <s:Body>
                <u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                    <InstanceID>0</InstanceID>
                    <CurrentURI>http://""" + ("%s:%s" % (MY_IP, MY_PORT)) + """/stream.mp3</CurrentURI>
                    <CurrentURIMetaData>&lt;?xml version="1.0" encoding="utf-8"?&gt;
                        &lt;DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/" xmlns:sec="http://www.sec.co.kr/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"&gt;
                            &lt;item id="0" parentID="0" restricted="1"&gt;
                                &lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;
                                &lt;dc:title&gt;&lt;/dc:title&gt;
                                &lt;dc:creator&gt;&lt;/dc:creator&gt;
                                &lt;upnp:artist&gt;&lt;/upnp:artist&gt;
                                &lt;upnp:albumArtURI&gt;/&lt;/upnp:albumArtURI&gt;
                                &lt;upnp:album&gt;&lt;/upnp:album&gt;
                                &lt;res protocolInfo="http-get:*:audio/mpegurl:DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000"&gt;http://""" + ("%s:%s" % (MY_IP, MY_PORT)) + """/stream.mp3&lt;/res&gt;
                            &lt;/item&gt;
                        &lt;/DIDL-Lite&gt;
                    </CurrentURIMetaData>
                </u:SetAVTransportURI>
            </s:Body>
        </s:Envelope>
    """)
).content)

print(requests.post(
    CLIENT_URL + avtransport_control_url,
    headers={
        "Content-type": "text/xml; charset=utf-8",
        "SOAPAction": "\"urn:schemas-upnp-org:service:AVTransport:1#Play\""
    },
    data=textwrap.dedent("""\
        <?xml version="1.0" encoding="utf-8" standalone="yes"?>
        <s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
            <s:Body>
                <u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                    <InstanceID>0</InstanceID>
                    <Speed>1</Speed>
                </u:Play>
            </s:Body>
        </s:Envelope>
    """)
).content)

server_thread.join()

Задержка воспроизведения составляет секунд 5, но в таких хоромах это никому не мешает. Sven SPS-605 тоже работают приемлемо, на громкости, необходимой, чтобы перекрыть звук льющейся воды, не перегружаются. Завтра съезжу за вторым Nakamichi MR-01 для устройства музыки на кухне и в Леруа за клеммниками, чтобы запараллелить колонки с освещением ванной!

Комментарии (2)
anonymous
12 сентября 2016, 12:40
S Q #1
Автореверс охуителен!
А можно мне с пластинками так же? :3
Автореверс охуителен! А можно мне с пластинками так же? :3
themylogin
12 сентября 2016, 17:53
S Q #2
Для пластинок есть Sharp RP-117, но это совсем неинтересно.
Лучше иметь Technics SL-1350 или 1650 и все пластинки в двух экземплярах!
Для пластинок есть Sharp RP-117, но это совсем неинтересно. Лучше иметь Technics SL-1350 или 1650 и все пластинки в двух экземплярах!
Добавить комментарий
Войдите через  Доступные тэги: <b>, <i>, <s>, <u>, <a href="">, <img src="" /> (загрузить), <pre>, <quote>
Теги
1nsk C++ E-Business GNU/GPL-софт HTML/CSS ICQ IDE KDE last.fm Linux Linuxnsk Microsoft Motivator MySQL P2P Party PCI PHP Python Qt SATA SEO Sibnet thelogin.ru Timelapse USB VHDL Web 2.0 Webstream Windows Wireless А я говорил! Авто Админ Алкоголь Аниме Атмосфера Аудио Безобразие Бизнес Блоги Боты Бред Быт Велосипед Весна Вечер Взлом Видеозахват Винил Винтаж Вконтакте Вода Воспоминания Гетто Гламур Гопота Горский Графити Грузовики Дача Девушки Деньги Дизайн Домофон Драки Европа Еда Железо Затулинка Зима Игрушки Игры Идея Интересно Интернет Исследование История Казань Карикатуры Квартира Класс Концерты Коромшук Коты КПК Красиво Криминал Крыши Курение Лето Литература Лицей НГТУ Лохи Магнитофоны Маркса Мат Математика Машинный перевод Мегафон Менты Метро Мечты Мифы Мне стыдно Мобайл Можга Москва МТС Музыка Мультикассы Мультфильмы Мы помним Надписи Наука Недвижимость Непоняятно Новосибирск Ночь Обидно Обман Образование Омск Опечатки Орфография Осень Парк Пиратство Плохо Погода Поезда Политика Помойка Праздники Презрение Природа Провайдеры Программирование Протест Психология Путешествия Радиотехника Растения Реклама Религия Ремонт Рисунки Руины Рэп Салют Серверы Сервис Сериалы Скриншоты Смешно СМИ Снег Собаки Совет Софт Спам Спасём Россию Стоп-ляп Страх Стройка Студяга Танцы ТВ Трамваи Транс Транспорт Уважаю Ужас Умный дом Утро Филдрепорты Философия Форум ФПМИ Франция Хикки Хостинг Цитатник Школа 208 ЭГ Эксперимент Юго-Западный