Мини Hi-Fi
Мини Hi-Fi

Привет, Хабр!

У меня уже давно назревала мысль о сборке портативной беспроводной акустики для воспроизведения аудио потока со своих домашних устройств. Решения с применением технологии Bluetooth меня не устраивали, хотелось слышать качественный звук. И вот, одним январским вечером, собрав свою волю в кучу, я решил реализовать задуманное. А что из этого вышло — читайте далее.

▢ Почему Wi-Fi?

Здесь я Америку для большинства не открою, всё предельно просто: чем больше ширина канала, тем качественнее музыка, ведь нам не нужно «пережимать» аудио поток, чтобы уместить его в узкий канал, а значит «на другом конце» мы имеем исходное качество с минимальным преобразованием. С Bluetooth дела обстоят иначе, хотя с каждой новой версией протокола ситуация улучшается.

▢ Технологии потоковой передачи аудио

В настоящее время в лидерах две технологии AirPlay и DLNA. Протокол AirPlay разработан компанией Apple и применяется в своей экосистеме. DLNA — набор стандартов, позволяющих совместимым устройствам передавать и принимать по домашней сети различный медиаконтент (изображения, музыку, видео), а также отображать его в режиме реального времени. Данные технологии уже стали стандартом беспроводной потоковой передачи аудио в Hi-End классе и широко используются в этом оборудовании. Мы не станем отставать от тренда — также возьмём за основу данные стандарты и в нашем проекте.

❯ Аппаратная платформа акустики

Одним из минусов реализации аудио стриминга по Wi-Fi каналу — это необходимость в бо́льших вычислительных мощностях, чем при передаче звука по Bluetooth. Но к счастью, у меня уже давно лежит в темном углу без дела плата МangoPi МQ-Quad на чипе Allwinner H616, купленная в 2022 году в процессе поиска дешевой SBC для моего проекта голосового ассистента. На тот момент плата была передовой по аппаратной начинке, но, к сожалению, программная часть подвела. У данной платы есть одна хорошая особенность: имеются контактные площадки для линейного выхода аудио сигнала, что является редкостью для данного форм-фактора одноплатных компьютеров.

МangoPi МQ-Quad линейный выход
МangoPi МQ-Quad линейный выход

Благодаря этому фактору, данная плата была выбрана и обрела вторую «жизнь» в нашем проекте.

И если мы посмотрим даташит SoС Allwinner H616 в разделе аудио, то мы увидим следующие характеристики встроенного ЦАП:

Характеристики аудиовыхода
Характеристики аудиовыхода

Как можно видеть, данные характеристики вполне соответствуют стандарту Hi-Res:

High-resolution audio (high-definition audio or HD audio) is a term for audio files with greater than 44.1 kHz sample rate or higher than 16-bit audio bit depth. It commonly refers to 96 or 192 kHz sample rates.

Это гарантирует высокое качество звука аудиовыхода данной платы.

▢ Усилитель мощности низкой частоты

Пожалуй, это один из важных элементов нашего проекта, от которого будет зависеть качество звучания акустики. Много аудиофилов пало на фоне мук выбора данного элемента, но мы точно знаем чего хотим и что мы имеем. Поэтому я не буду ходить вокруг да около и скажу, что мой выбор это усилитель класса D на базе микросхемы pam8403.

Выбор обусловлен следующим:

  • Уровень питающего напряжения соответствует бортовому 5 В;

  • Выходная мощность 3 Вт на канал, что вполне достаточно для моих целей;

  • Неплохие характеристики по шуму и гармоническим искажениям;

  • Высокий КПД, избавляет от необходимости использования радиатора;

  • Я уже работал с данной микросхемой и имею понимание о качестве звука, а оно достаточно хорошее (при правильной схемотехнике) в данном классе усилителей мощностью до 3 Вт.

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

▢ Изготовление платы усилителя мощности

Плата усилителя проектировалась САПР KiCAD. Ниже представлена принципиальная схема нашего усилителя.

Схема усилителя мощности
Схема усилителя мощности

Несмотря на то, что в даташите имеется надпись «Filterless 3W Class-D Stereo Audio Amplifier», которая по-английски нам намекает в отсутствии необходимости использования выходных LC фильтров, мы не послушаемся и применим данные фильтры в нашей схеме. Как показывает практика, применение выходных LC фильтров в данной схеме избавляет нас от некоторых негативных эффектов в процессе эксплуатации усилителя. Ниже на фото представлена трассировка платы и габаритные размеры.

Трассировка платы
Трассировка платы

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

Визуализация печатной платы усилителя
Визуализация печатной платы усилителя

▢ Изготовление печатной платы

Плата изготавливалась моим любимым методом, с помощью портативного лазерного станка (ссылка на метод в конце статьи). Спустя некоторое время, получаем следующий результат.

Изготовленная печатная плата усилителя
Изготовленная печатная плата усилителя

❯ Корпус блока электроники

3D печать давно уже проникла в жизнь DIY-щика, поэтому нам ничего не стоит разработать и напечатать корпус для нашего проекта. Разработка корпуса выполнялась в САПР FreeCAD и в соответствии с дизайном колонок. Ниже представлен рендер моего творения.

Рендер корпуса
Рендер корпуса

Для компоновки электроники также была разработана модель шасси. Ниже отображен её рендер.

Шасси электроники
Шасси электроники

Для обеспечения охлаждения платы SBC, передняя и тыловая панель имеет небольшой зазор, а наклонная «крыша» корпуса обеспечивает хорошую конвекцию воздуха.

После всех проверок размерности корпуса, отправляем всё это дело на печать.

❯ Сборка

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

Размещение электроники в шасси
Размещение электроники в шасси

Так как наша плата МangoPi МQ-Quad достаточно «шумная» (в плане электромагнитных помех), то нам необходимо установить наш усилитель экраном вверх. И далее подключим платы.

Подключенные платы
Подключенные платы

Обратите внимание, что для фильтрации шумов в цепи питания используются ферритовые кольца, которые являются эффективным фильтром высокочастотных помех в линии питания.

Как вы могли заметить, для автономного питания нашей системы используется Li-ion аккумулятор форм-фактора 18650 и емкостью 2200 мАч. Плата зарядки и защиты реализована на модуле с применением TP4056. Для формирования питающего напряжения 5 В, применяется повышающий DC-DC преобразователь на базе MT3608. Данные компоненты достаточно распространены на маркетплейсах и стоят $1 за вагон.

После подключения всех компонентов, выполняем тестовое включение.

Тестовое включение
Тестовое включение

Если на этом этапе ничего не взорвалось и не сгорело, то продолжаем монтировать всё это добро в корпус.

В полусобранном виде
В полусобранном виде

Изначально я не планировал использовать дисплей в своей конструкции, но аппетит приходит во время еды. Поэтому при дальнейшей реализации мне очень захотелось использовать OLED дисплей, тем более он давно у меня валялся без дела, а акустика без дисплея мне казалась скучной.

Далее делаем финальные штрихи и устанавливаем переднюю и тыловую панель. В итоге получаем следующий вид.

Блок электроники в собранном виде
Блок электроники в собранном виде

Изначально панели планировалось печатать черным цветом, но в процессе реализации у меня закончился черный филамент и пришлось печатать тем, что было. Глядя на фото вам может показаться, что качество печати не очень. Не переживайте, вам не показалось. Это отладочный образец корпуса, напечатанный с плохим качеством в пользу скорости печати. После того, как только у меня появится новый филамент, я обязательно напечатаю корпус в хорошем качестве.

❯ Программное обеспечение

Теперь к самому интересному. Прежде чем продолжить, у вас уже должна быть установлена операционная система Debian 12 (Bookworm) и плата подключена к сети. Для установки ПО, воспользуемся командной строкой.

Для исключения щелчков при включении нашей акустики, в усилите используется разрешающий сигнал «EN» (MUTE), который предназначен для активации усилителя. Чтобы иметь возможность управления усилителем, установим необходимый пакет. Для управления GPIO используется официальная библиотека Orange Pi wiringPi.

sudo apt-get update
sudo apt-get install -y git
git clone https://github.com/orangepi-xunlong/wiringOP.git
cd wiringOP
sudo ./build clean
sudo ./build

Для управления GPIO используется следующие команды:

gpio mode   18 out  # Изменение типа пина вход/выход (in/out)
gpio write  18 0    # Изменение уровня пина низкий/высокий (0/1)
gpio read   18      # Чтение состояние пина

Для управления усилителем мы будем использовать 18-й пин. Посмотреть расположение пинов на PRI можно с помощью команды:

gpio readall

▢ Конфигурация звуковой подсистемы

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

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

aplay -l

выведем список всех доступных устройств для воспроизведения. После выполнения команды мы видим следующий список устройств:

**** List of PLAYBACK Hardware Devices ****
card 0: sndahub [sndahub], device 0: Media Stream sunxi-ahub-aif1-0 [Media Stream sunxi-ahub-aif1-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: sndahub [sndahub], device 1: System Stream sunxi-ahub-aif2-1 [System Stream sunxi-ahub-aif2-1]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: sndahub [sndahub], device 2: Accompany Stream sunxi-ahub-aif2-2 [Accompany Stream sunxi-ahub-aif2-2]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: allwinnerhdmi [allwinner-hdmi], device 0: hdmi i2s-hifi-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: Codec [H616 Audio Codec], device 0: CDC PCM Codec-0 [CDC PCM Codec-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

В нашем случае нам необходимо использовать устройство card 2 — именно оно является линейным выходом. Чтобы назначить данное устройство в качестве устройства воспроизведения на умолчанию, необходимо создать файл конфигурации:

nano /etc/asound.conf

И добавить в него следующее содержание:

pcm.!default {
    type plug
    slave.pcm {
        type dmix
        ipc_key 1024
        slave {
            pcm "hw:2"
            period_time 0
            period_size 1920
            buffer_size 19200
        }
    }
}
ctl.!default {
    type hw
    card 2
}

Секция ctl.!default отвечает за установку аудио устройства по умолчанию, а pcm.!default дополнительная конфигурация аудио карты — конфигурация подобрана таким образом, чтобы исключить неблагоприятные эффекты (прерывание и т. п.) в процессе воспроизведения. Далее мы можем настроить уровни, выполнив следующую команду:

alsamixer

И выполнить настройку как показано на скриншоте:

ALSA Mixer
ALSA Mixer

Теперь мы можем проверить корректность воспроизведения с помощью тестовой команды:

aplay /usr/share/sounds/alsa/audio.wav

Но перед этим необходимо активировать усилитель с помощью команды:

gpio mode   18 out  # Изменение типа пина вход/выход (in/out)
gpio write  18 1    # Изменение уровня пина низкий/высокий (0/1)

Если всё шаги были выполнены верно, то вы услышите воспроизведение аудио файла.

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

sudo nano /etc/systemd/system/sound_on.service

И добавим в него следующее содержимое:

[Unit]
Description=Sound ON Pin Service
After=network.target
After=gmediarender.service

[Service]
ExecStartPre=/usr/local/bin/gpio mode 18 out
ExecStart=/bin/aplay /usr/share/sounds/alsa/audio.wav
ExecStartPost=/bin/sleep 3
ExecStartPost=/usr/local/bin/gpio write 18 1

[Install]
WantedBy=multi-user.target

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

systemctl enable sound_on.service

▨ Установка аудио рендереров

Здесь речь идет о ПО, которое непосредственно занимается приемом и воспроизведением аудио потока AirPlay и DLNA.

▢ DLNA рендерер

Для наших целей будем использовать популярный и высокоэффективный Gmrender-Resurrect. Ниже представлены шаги по установке данного рендерера.

Установка дополнительных зависимостей для возможности компиляции ПО:

sudo apt-get install build-essential autoconf automake libtool pkg-config

Установка дополнительных библиотек, которые использует рендерер для своей работы:

sudo apt-get update
sudo apt-get install libupnp-dev libgstreamer1.0-dev \
             gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
             gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
             gstreamer1.0-libav

И дополнительные зависимости для работы нашей аудио подсистемы:

sudo apt-get install gstreamer1.0-alsa

Скачиваем репозиторий проекта:

git clone https://github.com/hzeller/gmrender-resurrect.git

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

cd gmrender-resurrect
./autogen.sh
./configure
make

И установим рендерер:

sudo make install

Теперь используя команду:

gmediarender

Мы можем запускать наш рендерер.

Теперь нам нужно создать сервис для автозапуска нашего рендерера. Создаём файл сервиса:

sudo nano /etc/systemd/system/gmediarender.service

И добавляем следующее содержимое:

[Unit]
Description=DLNA Renderer
After=network.target

[Service]
#User=orangepi
#Group=orangepi
ExecStartPre=/bin/sleep 30
ExecStart=/usr/bin/gmediarender --friendly-name "Wi-Fi CYBEREX Speaker" interface=wlan0
Nice=-20
Restart=always

[Install]
WantedBy=multi-user.target

И добавляем сервис в автозагрузку:

systemctl enable gmediarender.service

▢ AirPlay рендерер

В качестве AirPlay рендерера будем использовать проект Shairport Sync. Для установки нам понадобятся дополнительные пакеты.

sudo apt-get install libdaemon-dev libasound2-dev \
                     libpopt-dev libconfig-dev avahi-daemon \
                     libavahi-client-dev libssl-dev

Копируем репозиторий проекта:

git clone https://github.com/mikebrady/shairport-sync.git

Перейдем в папку с исходниками, сконфигурируем и скомпилируем:

cd shairport-sync
autoreconf -i -f
 ./configure --sysconfdir=/etc --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-systemd
make

Создадим группу и пользователя для Shairport:

getent group shairport-sync &>/dev/null || sudo groupadd -r shairport-sync >/dev/null
getent passwd shairport-sync &> /dev/null || sudo useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync >/dev/null

Установим:

sudo make install

И добавим сервис в автозапуск:

sudo systemctl enable shairport-sync

Изменение названия AirPlay рендерера. Редактируем файл конфигурации:

sudo nano /etc/shairport-sync.conf

Раскомментируем строку и введем свое имя:

name = "Wi-Fi CYBEREX Speaker"

После всех проделанных шагов нам необходимо перезагрузить систему:

sudo shutdown -r now

На этом этапе мы уже имеем рабочую акустику с протоколами AirPlay и DLNA.

❯ Дисплей

Как я уже говорил выше, акустика без дисплея мне показалась скучной, поэтому я решил добавить и его в систему, реализовав отображение метаданных воспроизводимого потока и часы в процессе простоя. В качестве дисплея используется OLED модуль SSD1306 с разрешением 64 х 48, а всю логику управления дисплеем реализовал в небольшом Python скрипте.

Код управления дисплеем
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import ImageFont
from threading import Thread
import time
import datetime
import math
import requests
from xml.etree import ElementTree as ET
import socket

# Настройка I2C интерфейса
serial = i2c(port=0, address=0x3C)  # 0x3C - адрес для SSD1306
device = ssd1306(serial, width=64, height=48)
# Загрузка шрифта с поддержкой кириллицы
font_path = "/usr/share/fonts/truetype/ubuntu/UbuntuMono-R.ttf"  # Шрифт
font = ImageFont.truetype(font_path, 12, encoding='UTF-8')  # размер шрифта
font_b = ImageFont.truetype(font_path, 16, encoding='UTF-8')  # размер шрифта  
# Url сервиса
# Получаем имя хоста
hostname = socket.gethostname()
# Получаем IP-адрес по имени хоста
ip_address = socket.gethostbyname(hostname)
 
service_url = f"http://{ip_address}:49494/upnp/control/rendertransport1"
# Текст и начальная позиция бегущей строки
title_d  = ""
artist_d = "ГОТОВ К ПОДКЛЮЧЕНИЮ"
album_d  = ""
track_time = ""
current_time_arh = ""
counter_end = 0
en = False

def set_bool(bools):
    global en
    en = bools
   # print(en)

def read_bool():
    global en
    return en

# Делаем часики
def draw_clock(draw, now):
    center_x = 32
    center_y = 24
    radius = 25
    # Делаем рамку с закруглением
    draw.rectangle(device.bounding_box, outline="black", fill="black")
    draw.rounded_rectangle(device.bounding_box, radius=8, outline="white", fill="black")
    # Часовая 
    hour_angle = 2 * math.pi * (now.hour % 12 + now.minute / 60) / 12
    hour_x = center_x + int(radius * 0.5 * math.sin(hour_angle))
    hour_y = center_y - int(radius * 0.5 * math.cos(hour_angle))
    draw.line((center_x, center_y, hour_x, hour_y), fill="white")

    # Минутная 
    minute_angle = 2 * math.pi * now.minute / 60
    minute_x = center_x + int(radius * 0.7 * math.sin(minute_angle))
    minute_y = center_y - int(radius * 0.7 * math.cos(minute_angle))
    draw.line((center_x, center_y, minute_x, minute_y), fill="white")

    # Секундная
    second_angle = 2 * math.pi * now.second / 60
    second_x = center_x + int(radius * 0.9 * math.sin(second_angle))
    second_y = center_y - int(radius * 0.9 * math.cos(second_angle))
    draw.line((center_x, center_y, second_x, second_y), fill="white")

    # Рисуем круг циферблата
    # draw.ellipse((center_x - radius, center_y - radius, center_x + radius, center_y + radius), outline="white")
    # Делаем рамку с закруглением
    #draw.rounded_rectangle(device.bounding_box, radius=5, outline="white", fill="black")

def display_print():
    start_pix_t = 64
    start_pix_ar = 64
    start_pix_al = 64
    while True:
        if read_bool():
            title_width  = len(title_d) * 6  # Ожидаемая ширина текста (по 6 пикселей на символ)
            artist_width = len(artist_d) * 6
            album_width  = len(album_d) * 6
            max_width = max(title_width, artist_width, album_width)
            with canvas(device) as draw:
               if title_d == "swyh-rs":
                   # draw.text((start_pix_t, 1), title_d, fill="white")
                   draw.text((1, 12), "ПК АУДИО", fill="white", font=font_b)
                   #draw.text((start_pix_al, 24), album_d, fill="white")
                   draw.text((15, 36), track_time, fill="white")
               else:
               # Прокрутка текста
               # draw.rectangle(device.bounding_box, outline="white", fill="black")
                 draw.text((start_pix_t, 1), title_d, fill="white", font=font)
                 draw.text((start_pix_ar, 12), artist_d, fill="white", font=font)
                 draw.text((start_pix_al, 24), album_d, fill="white", font=font)
                 draw.text((15, 36), track_time, fill="white")

            # Сдвиг текста влево
            if title_width > 64:
                start_pix_t  -= 1
            else:
              start_pix_t = 1

            if artist_width > 64:
               start_pix_ar  -= 1
            else:
              start_pix_ar = 1

            if album_width > 64:
                start_pix_al -= 1
            else:
              start_pix_al = 1

            # Если текст полностью вышел за экран, вернем его в начальную позицию
            if start_pix_t < -max_width:
                start_pix_t = 64

            if start_pix_ar < -max_width:
                start_pix_ar = 64
            if start_pix_al < -max_width:
                start_pix_al = 64

            time.sleep(0.05)  


# Получение данных с рендеринга
# Функция для разбора CurrentURIMetaData
def parse_metadata(metadata):
    global title_d
    global artist_d
    global album_d
    if metadata:
        # Парсим метаданные как XML
        root = ET.fromstring(metadata)
        namespace = {'didl': 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/',
                     'dc': 'http://purl.org/dc/elements/1.1/',
                     'upnp': 'urn:schemas-upnp-org:metadata-1-0/upnp/'}

        # Извлекаем информацию о треке
        title = root.find('.//dc:title', namespace)
        artist = root.find('.//upnp:artist', namespace)
        album = root.find('.//upnp:album', namespace)
        album_art = root.find('.//upnp:albumArtURI', namespace)

        title_d = title.text if title is not None else "Unknown"
        artist_d = artist.text if artist is not None else "Unknown"
        album_d  = album.text if album is not None else "Unknown"

        #print("Track Information:")
        #print(f"  Title: {title.text if title is not None else 'Unknown'}")
        #print(f"  Artist: {artist.text if artist is not None else 'Unknown'}")
        #print(f"  Album: {album.text if album is not None else 'Unknown'}")
        #print(f"  Album Art URI: {album_art.text if album_art is not None else 'None'}")
    #else:
        #print("No metadata available.")

# Функция для получения временных меток
def get_position_info():
    global track_time
    global current_time_arh
    global title_d
    global counter_end
    # Заголовки и тело SOAP-запроса
    headers = {
        "Content-Type": 'text/xml; charset="utf-8"',
        "SOAPAction": '"urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo"',
    }

    body = """<?xml version="1.0" encoding="utf-8"?>
    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
                s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <s:Body>
        <u:GetPositionInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
          <InstanceID>0</InstanceID>
        </u:GetPositionInfo>
      </s:Body>
    </s:Envelope>"""

    # Отправляем запрос
    response = requests.post(service_url, headers=headers, data=body)

    # Разбираем результат
    if response.status_code == 200:
        xml_response = ET.fromstring(response.content)
        rel_time = xml_response.find('.//RelTime').text  # Текущее время
        track_duration = xml_response.find('.//TrackDuration').text  # Общая длительность трека
        track_time = rel_time
        if current_time_arh == rel_time:
            if counter_end > 10:
                set_bool(False)
            counter_end += 1
        else:
          current_time_arh = rel_time
          set_bool(True)
          counter_end = 0
        #print("Playback Position Info:")
        #print(f"  Current Time: {rel_time}")
        #print(f"  Track Duration: {track_duration}")
    #else:
        #print(f"Error getting position info: {response.status_code}, {response.text}")

def read_data_from_renderer():
    # Функция для получения информации о воспроизводимом медиа с рендерера.
    
    # Заголовки для GetMediaInfo
    headers = {
        "Content-Type": 'text/xml; charset="utf-8"',
        "SOAPAction": '"urn:schemas-upnp-org:service:AVTransport:1#GetMediaInfo"',
    }

    # Тело SOAP-запроса для GetMediaInfo
    body = """<?xml version="1.0" encoding="utf-8"?>
    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
                s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <s:Body>
            <u:GetMediaInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                <InstanceID>0</InstanceID>
            </u:GetMediaInfo>
        </s:Body>
    </s:Envelope>"""

    # Отправляем запрос на GetMediaInfo
    response = requests.post(service_url, headers=headers, data=body)

    # Проверяем ответ
    if response.status_code == 200:
        # Парсим XML ответа
        xml_response = ET.fromstring(response.content)
        # Извлекаем данные о медиа
        nr_tracks = xml_response.find('.//NrTracks').text
        current_uri = xml_response.find('.//CurrentURI').text
        metadata = xml_response.find('.//CurrentURIMetaData').text

       # print("Basic Media Info:")
       # print(f"  Number of Tracks: {nr_tracks}")
       # print(f"  Current URI: {current_uri}")

        # Разбираем метаданные
        parse_metadata(metadata)

        # Получаем временные метки
        get_position_info()

    #else:
        #print(f"Error: {response.status_code}, {response.text}")

# Функиця для периодического вызова
def periodic_task():
    while True:
        read_data_from_renderer()
        time.sleep(1)  # Задержка в 1 секунду

def periodic_task_clock():
    while True:
        if not read_bool():
            now = datetime.datetime.now()
            with canvas(device) as draw:
                draw.rectangle(device.bounding_box, outline="white", fill="black")
                draw_clock(draw, now)
        time.sleep(1)

# Запуск потока для отображения текста
t1 = Thread(target=display_print, name='t1')
t1.start()

# Запуск переодического опрома медиа
t2 = Thread(target=periodic_task, name='t2')
t2.start()

# Запуск переодического круглые часы
t3 = Thread(target=periodic_task_clock, name='t3')
t3.start()

Для работы скрипта нам необходимо установить следующие пакеты:

Python 3.11

sudo apt-get install python3.11
sudo apt-get install python3.11-dev

Установщик пакетов pip:

sudo apt-get install python3-pip

Затем нам нужно создать виртуальное окружение:

python3 -m venv myvenv

И активируем данное окружение для последующей установки pip пакетов:

source myvenv/bin/activate

Взаимодействовать с дисплеем мы будем с помощью библиотеки Luma Oled, для которой необходим пакет spidev:

pip install spidev

И затем установим Luma Oled:

pip install luma.oled

Чтобы скрипт дисплея запускался автоматически при старте системы, создадим файл системного сервиса:

sudo nano /etc/systemd/system/oled_display.service

В файл добавим следующее содержание:

[Unit]
Description=OLED Display Service
After=network.target
After=gmediarender.service

[Service]
ExecStart=/root/myvenv/bin/python3 /home/orangepi/media_info_disp.py
WorkingDirectory=/home/orangepi
Environment="PATH=/root/myvenv/bin:/usr/bin:/bin"
StandardOutput=inherit
StandardError=inherit
Restart=always

[Install]
WantedBy=multi-user.target

Активируем автозагрузку:

sudo systemctl enable oled_display.service

Ниже показаны три режима работы дисплея:

Режимы работы дисплея
Режимы работы дисплея

режим отображения метаданных проигрываемого файла, режим бездействия (отображаются часы, дизайн которых я уже изменил) и режим приема системного звука ПК.

❯ Воспроизведение системного звука ПК

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

Данный режим прекрасно работает на macOS из коробки и имеет встроенное решение на базе AirPlay, чего нельзя сказать о Windows и Linux системах. Но мне удалось найти работающее решение, которое позволяет реализовать данную функцию на системах с Windows и Linux на базе DLNA, несмотря на то, что данная функция официальное не поддерживается стандартом DLNA.

❯ Итоги

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

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

Ниже под спойлером размещены пару коротких видео для оценки качества звучания (насколько это позволяют микрофоны смартфона).

Видео демонстрации

Спасибо за внимание! Всем добра, здоровья и интересных проектов!

Ссылки к статье:


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud  в нашем Telegram-канале 

Перейти ↩

? Читайте также:

Комментарии (29)


  1. alprk
    10.02.2025 10:13

    Очень интересно, но не все понятно - как выглядит подключение этого всего к Linux через DLNA? Через чтото типа https://pypi.org/project/pa-dlna/? Работает ли это с PipeWire? Заработает ли это с Android?


    1. CyberexTech Автор
      10.02.2025 10:13

      Если речь идет о трансляции системного звука, то я использую это решение, если вы имеете ввиду просто воспроизведение аудио файлов, то я использую это. На Windows воспроизводить аудио файлы на DLNA рендерере можно с помощью стандартного медиаплеера. Для Android есть популярное решение BubbleUPnP. Для устройств Apple уже все работает из «коробки».


  1. Tirarex
    10.02.2025 10:13

    Ну с качественным звуком вы перегнули.

    У меня были колонки с фото и усилитель PAM который используется в этой конструкции. Про колонки ровно 0 хороших слов, выполнены из мягкого пластика который положительно на звуке не сказывается, обьем под динамики никто не считал, бас слабый а вибраций много, вч почти нет, сч глухой. Сами динамики тоже смешные, на моих было написано 5W, собственно на мощности 5вт в течении 1-2 минут они сгорали.

    Усилитель pam8403 валяется как мусор, страшно песочит, требует очень хороших фильтров, реальная мощность около 1-2вт при 5в входе (без клиппинга и диких потерь качества).

    Конечно если учитывать что колонки как и усилитель можно буквально по 100 рублей взять, претензий к ним быть не может, но как говорится - you get what you pay for.


  1. anonymous
    10.02.2025 10:13


    1. Alex-Freeman
      10.02.2025 10:13

      Поддержу, динамики редкое г.., валяются в коробке, после пары дней убрал.

      Кстати кто ни будь может порекомендовать плату нормального усилителя класса D с Aлика? Мощность не критична, главное чтобы с 8 омами нормально работала и качество звука было хорошим, просто валяется центральный канал Dynaudio, хочу его переделать в переносную стереосистему)


      1. gvitaly
        10.02.2025 10:13

        PAM8406 меньше "шипит" во время тишины, по сравнению с 8403. Можно его попробовать.

        По остальным характеристикам совсем чуть-чуть лучше.
        По остальным характеристикам совсем чуть-чуть лучше.


        1. CyberexTech Автор
          10.02.2025 10:13

          ..


          1. gvitaly
            10.02.2025 10:13

            Я делал для pam8403 выходной фильтр с ферритовыми дросселями и конденсаторами 220 пф, как и указано в даташите. На глаз слух разницы особо не было.


        1. Alex-Freeman
          10.02.2025 10:13

          А китайский Хай Энд качественные усилители в классе D существуют, с небольшой мощностью(до 50 - 100W), но высоким качеством (бюджет вторичен)? Просто попадались платы на алике по 20к - 40к+ руб, но брать без отзывов или с отзывом типа "отличный усилитель - работает" не хочется).


          1. gvitaly
            10.02.2025 10:13

            Ниже упомянули tpa3255.


    1. CyberexTech Автор
      10.02.2025 10:13

      Усилитель pam8403 валяется как мусор, страшно песочит, требует очень хороших фильтров

      При нормальной схемотехнике, вполне отличный усилитель для данного класса мощности. Это как с борщом, название одно, а готовят все по разному.

      Про колонки ровно 0 хороших слов, выполнены из мягкого пластика который положительно на звуке не сказывается, обьем под динамики никто не считал, бас слабый а вибраций много, вч почти нет, сч глухой.

      В моем случае, используются нормальные динамические головки с хорошим магнитом, сопротивлением 6 Ом и мощностью 4 Вт. По АЧХ тоже нет больших нареканий. Низкие, средние и высокие частоты воспроизводятся на хорошем уровне. К акустическому оформлению тоже нет особых претензий, единственное что я сделал - это проклеил стыки.


  1. dkfbm
    10.02.2025 10:13

    как я делал беспроводную мини акустику с качественным звуком

    "Мини-акустика" и "качественный звук" – это оксюморон. Довольно странно заморачиваться характеристиками тракта, если у него на конце однополосная пластмассовая коробочка с неизвестного качества динамиком, выполненная без каких-либо расчётов именно что акустики.


    1. mclander
      10.02.2025 10:13

      Заморачивать с крутыми динамиками довольно странно, без учёта характеристик комнаты. По идее надо хотя бы делать квадро, чтобы колонки по углам перекрывали паразитные отражения звука от стен, пола потолка и прочих шкафов)


      1. dkfbm
        10.02.2025 10:13

        По идее надо хотя бы делать квадро

        Да вроде эта мода давно прошла уже, все Hi End системы нынче вполне обходятся парой колонок. У меня стоит Dolby Prologic – но ни разу не использовалась в таком качестве, просто в силу отсутствия соответствующих источников. Да и непрактично это очень, столько проводов тянуть.


  1. REPISOT
    10.02.2025 10:13

    Hi-Fi с Wi-Fi или как я делал беспроводную мини акустику с качественным звуком

    Такой заголовок налагает на автора обязательства раскрыть полученное "качество" звука. А в статье нет никаких характеристик полученного устройства.


    1. rexen
      10.02.2025 10:13

      Да какое тут может быть качество, вы на компоненты взгляните. Тут даже до hi-fi не тянет. Идея очень интересная, но реализация очень далека от заявки. Самое главное - нет сравнения с ширпотребным Блютус-вариантом. Ведь претензия была именно к нему.


  1. Tomasina
    10.02.2025 10:13

    Ну разве может быть качественный звук в миниатюрных тонкостенных пластиковых коробочках? Думаю, схема старается улучшить качество, но аппаратно косяк - что до, что после - будет унитазный звук, в силу материально- и конструктивных особенностей.


    1. CyberexTech Автор
      10.02.2025 10:13

      Ох, если бы всё зависло только от акустики - тогда бы мир заиграл новыми красками: подключил качественную акустику к плохому усилителю и наслаждаешься звуком. К сожалению, мир жесток и акустика это всего лишь последний элемент, с помощью которого достигается качественный звук. Даже если используя эти «пластиковые коробочки» провести тест и подключить их к разным по качеству усилителям, то вы однозначно заметите различие по качеству звучания. Эти «пластиковые коробочки» покупались в качестве донора динамиков, которые достаточно неплохи для данной ценовой категории. Изначально я планировал изготовить корпус акустики самостоятельно, но послушав их звучание, меня устроило их акустическое оформление и я решил оставить всё как есть с некоторой доработкой.


      1. syrd
        10.02.2025 10:13

        Вопрос в том, что услышите ли Вы разницу в качестве звука, если вместо МангоПИ с вай-фаем подключить к вашему усилителю с колонками обычный, не самой последней паршивости, блютуф модуль? Который, к тому же, коннектится со всем и вся безо всяких танцев с бубнами.


  1. Jury_78
    10.02.2025 10:13

    Главный вопрос - лучше чем BT?


    1. BSOZ
      10.02.2025 10:13

      Тут возникнет вопрос, какой с каким профилем BT сравнивать (в бюджетных адаптерах максимум поддерживается что-то 15-летней давности). А когда широкое распространение получат устройства с полноценной поддержкой LE Isochronous Channels или Channel Sounding, будет вообще сложно сравнивать BT с каналом через WiFi т.к. во втором случае для синхронизации каналов, можно сказать, вообще ничего не предусмотрено и прилично звучать будет только с 1 приёмником, а дальше только провода на пассивные компоненты. Ну или вообще в моно. Ужасное звучание Bluetooth колонок связано не с самим Bluetooth, а с реализацией.


    1. CyberexTech Автор
      10.02.2025 10:13

      Главный вопрос - лучше чем BT?

      Отсутствием пережатия и неограниченной шириной канала.


      1. GuessWho
        10.02.2025 10:13

        А что с задержкой? Подключаю по S/PDIF колонки только из-за этого, но стандарт начинает вымирать на новых материнках


        1. CyberexTech Автор
          10.02.2025 10:13

          Я не замерял, но она значительно ниже, чем при BT подключении. При трансляции звука при просмотре фильма, задержка не ощущается, словно акустика подключена по кабелю.


      1. Jury_78
        10.02.2025 10:13

        Вопрос про звук, а не про реализацию.


  1. Anselm_nn
    10.02.2025 10:13

    кроме мусорного кодека sbc оставшегося с прошлого века есть чуть более интересные aptx, ldac

    есть еще и более интересный кодек, l2hc, который позволяет дать по мегабиту на каждое ухо. к сожалению, как запустить его на пк я не понял, поднялся только ldac. с телефоном работает, очень хорошее качество


    1. evtomax
      10.02.2025 10:13

      Даже AptX HD шипит на высоких частотах... А SBC, если принудительно установить тот же битрейт, что у AptX HD, может звучать даже лучше.


  1. rexen
    10.02.2025 10:13

    Это гарантирует высокое качество звука аудиовыхода данной платы

    Это ничего не гарантирует. Это чисто бумажные циферки. Чтобы из цифрового потока вытянуть ХайЭнд - нужно потрудиться. Там важно всё - и питание и ЦАПы и фильтры и усилитель.


  1. alexhott
    10.02.2025 10:13

    Про блютуз зря вы так, уже года 3 самые дешманские модули с али тот же mp3 играют не хуже чем по проводам с телефона. И на такой акустике с этим усилителем вы разницы с WIFI точно не услышите.
    А из усилков класса d я пока более менее нормальный звук услышал только на tpa3255, реально лучше всех остальных популярных звучит на одной и той же акустике. Но там тоже от реализации самой платы еще зависит, народ уже их допиливает даже.