Перейти к основному содержимому
Версия: Bbrain 2.0

Документация: ROS2 пакет media_driver

Общее описание

Пакет media_driver обеспечивает управление мультимедийным контентом на робоголове через два независимых экземпляра MPV-плеера:

  • Video player — отображает видео/изображения на дисплее через DRM (без X-сервера), аудиодорожка отключена
  • Audio player — воспроизводит аудиофайлы через ALSA, видеовыход отключен

Такая архитектура позволяет одновременно показывать визуальный контент на экране и воспроизводить звук. Дополнительно поддерживается потоковый вывод ROS-изображений на дисплей в реальном времени.

Также пакет содержит узел touch_publisher для чтения событий тачскрина из /dev/input/event* (evdev, multitouch) и публикации их в ROS2-топик.


Структура пакета

media_driver/
├── CMakeLists.txt # Файл для CMake сборки пакета
├── config
│ └── media_driver.yaml # Конфиг-файл
├── examples
│ ├── audio.aac # Файлы-примеры аудио в разных форматах
│ ├── audio.aiff
│ ├── audio.flac
│ ├── audio.m4a
│ ├── audio.mp3
│ ├── audio.ogg
│ ├── audio.opus
│ ├── audio.wav
│ ├── audio.wma
│ ├── imager.py # Скрипт для тестирования потокового вывода изображения с камеры на дисплей
│ ├── picture.bmp # Файлы-примеры картинок в разных форматах
│ ├── picture.gif
│ ├── picture.jpeg
│ ├── picture.png
│ ├── picture.svg
│ ├── picture.tif
│ ├── picture.webp
│ ├── touchscreen.py # Скрипт для тестирования работы тачскрина
│ ├── video.3gp # Файлы-примеры видео в разных форматах
│ ├── video.avi
│ ├── video.flv
│ ├── video.mkv
│ ├── video.mov
│ ├── video.mp4
│ ├── video.mpeg
│ ├── video.ts
│ ├── video.webm
│ └── video.wmv
├── include # Заголовочные файлы
│ └── media_driver
│ ├── mpv_player.hpp
│ ├── node.hpp
│ ├── touch_publisher.hpp
│ └── utils.hpp
├── launch # Launch-фалы
│ ├── media_driver.launch.py
│ └── touchscreen.launch.py
├── package.xml
├── README.md
└── src # Исходный код
├── main.cpp
├── mpv_player.cpp
├── node.cpp
├── touch_publisher.cpp
└── utils.cpp

Зависимости

Системные библиотеки

  • libmpv — медиаплеер
  • OpenCV — обработка изображений
  • ALSA — аудиовыход
  • Linux evdev (<linux/input.h>) — чтение событий тачскрина из /dev/input/event*

ROS2 пакеты

  • rclcpp — клиентская библиотека C++
  • sensor_msgs — стандартные сообщения для изображений
  • cv_bridge — конвертация ROS ↔ OpenCV
  • robohead_interfaces — пользовательские сервисы (PlayMedia, SimpleCommand)

Сборка и запуск

Сборка

cd ~/robohead_ws
colcon build --symlink-install --packages-select robohead_interfaces media_driver
source install/setup.bash

Запуск

# Запускает узел работы с медиаплеером (дисплей+аудио) и узел работы с тачскрином:
ros2 launch media_driver media_driver.launch.py

# Запускает только узел работы с медиаплеером:
ros2 launch media_driver player.launch.py

# Запускает только узел работы с тачскрином:
ros2 launch media_driver touchscreen.launch.py

При запуске используются параметры из конфиг-файла: media_driver/config/media_driver.yaml

Ожидаемый вывод (при запуске обоих узлов):

[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [media_driver_node-1]: process started with pid [29242]
[INFO] [touch_publisher_node-2]: process started with pid [29243]
[touch_publisher_node-2] [INFO] [1776414096.540013704] [media_driver.touch_publisher_node]: Found: Waveshare Waveshare -079-HD
[touch_publisher_node-2] [INFO] [1776414096.598018581] [media_driver.touch_publisher_node]: Touch range X:[0..4096] Y:[0..4096]
[touch_publisher_node-2] [INFO] [1776414096.598095618] [media_driver.touch_publisher_node]: Path: /dev/input/event4
[touch_publisher_node-2] [INFO] [1776414096.598231951] [media_driver.touch_publisher_node]: INITED
[media_driver_node-1] [INFO] [1776414096.664715001] [media_driver.media_driver]: [mpv_video] MPV player initialized successfully (type: VIDEO)
[media_driver_node-1] [INFO] [1776414096.672550045] [media_driver.media_driver]: [audio_mpv] MPV player initialized successfully (type: AUDIO)
[media_driver_node-1] [INFO] [1776414096.689966037] [media_driver.media_driver]: INITED

API: Сервисы

1. play_media — Воспроизведение медиа

Тип: robohead_interfaces/srv/PlayMedia
Полное имя: /media_driver/play_media

Поля запроса

ПолеТипОписание
path_to_video_filestringПуть к видео/изображению, URL потока или __STOP__ (stop_command можно изменить в конфиг-файле) для остановки.
Пустая строка — не трогать видеоплеер
path_to_audio_filestringПуть к аудиофайлу, URL потока или __STOP__ (stop_command можно изменить в конфиг-файле) для остановки.
Пустая строка — не трогать аудиоплеер
loopbooltrue — зацикленное воспроизведение, false — однократное

Поля ответа

ПолеТипЗначение
dataint160 — успех
-1 — ошибка (файл не найден, неподдерживаемый формат)

Поддерживаемые форматы

КатегорияРасширения
Изображения.png, .jpeg (.jpg), .bmp, .webp, .gif, .tiff (.tif), .svg
Видео.mp4, .mov, .avi, .mkv, .webm, .flv, .wmv, .mpeg, .3gp, .ts, .m3u3
Аудио.mp3, .wav, .ogg, .flac, .aac, .m4a, .wma, .opus, .aiff

Поддерживаемые протоколы

http://, https://

Примеры

Обратите внимание: пути до файлов могут отличаться на вашем устройстве

Воспроизвести аудио (тест всех форматов)

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.aac', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.aiff', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.flac', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.m4a', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.mp3', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.ogg', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.opus', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.wav', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.wma', loop: false}"

Воспроизвести видео (тест всех форматов)

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.3gp', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.3gp', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.avi', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.avi', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.flv', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.flv', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mkv', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mkv', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mov', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mov', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mp4', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mp4', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mpeg', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mpeg', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.ts', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.ts', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.webm', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.webm', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.wmv', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.wmv', loop: false}"

Вывести картинку (тест всех форматов)

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/picture.bmp', path_to_audio_file: '', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/picture.gif', path_to_audio_file: '', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/picture.jpeg', path_to_audio_file: '', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/picture.png', path_to_audio_file: '', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/picture.svg', path_to_audio_file: '', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/picture.tif', path_to_audio_file: '', loop: false}"

ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/picture.webp', path_to_audio_file: '', loop: false}"

Комбинированные примеры:

# Воспроизвести видео со звуком (без зацикливания)
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mp4', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mp4', loop: false}"

# Прекратить воспроизведение видео и звука, очистить экран
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '__STOP__', path_to_audio_file: '__STOP__', loop: false}"

# Воспроизвести видео без звука (без зацикливания). Если в момент вызова воспроизводился звук - он НЕ будет прерван.
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mp4', path_to_audio_file: '', loop: false}"

# Воспроизвести видео без звука (без зацикливания). Если в момент вызова воспроизводился звук - он БУДЕТ прерван.
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mp4', path_to_audio_file: '__STOP__', loop: false}"

# Воспроизвести интернет-радио
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: 'http://chanson.hostingradio.ru:8041/chanson256.mp3', loop: false}"

# Воспроизвести интернет-стрим
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: 'http://hls.mirtv.cdnvideo.ru/mirtv-parampublish/mir24_2500/playlist.m3u8', path_to_audio_file: 'http://hls.mirtv.cdnvideo.ru/mirtv-parampublish/mir24_2500/playlist.m3u8', loop: false}"


# Остановить только видео (аудио продолжит играть)
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '__STOP__', path_to_audio_file: '', loop: false}"

# Остановить только аудио
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '__STOP__', loop: false}"

# Зациклить аудио
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/audio.mp3', loop: true}"

# Зациклить видео+аудио
ros2 service call /media_driver/play_media robohead_interfaces/srv/PlayMedia \
"{path_to_video_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mp4', path_to_audio_file: '/home/pi/robohead_ws/src/robohead2/media_driver/examples/video.mp4', loop: true}"

2. set_volume — Установка громкости

Тип: robohead_interfaces/srv/SimpleCommand
Полное имя: /media_driver/set_volume

Поля запроса

ПолеТипОписание
dataint16Желаемая громкость (0–100)

Поля ответа

ПолеТипЗначение
dataint16Установленная громкость или -1 при ошибке

При выходе из диапазона от 0 до 100 устанавливает наиболее близкое допустимое значение.

Пример

ros2 service call /media_driver/set_volume robohead_interfaces/srv/SimpleCommand "{data: 75}"

3. get_volume — Получение громкости

Тип: robohead_interfaces/srv/SimpleCommand
Полное имя: /media_driver/get_volume

В поле запроса ничего задавать не нужно - значение игнорируется.

Поля ответа

ПолеТипЗначение
dataint16Текущая громкость (0–100) или -1 при ошибке

Пример

ros2 service call /media_driver/get_volume robohead_interfaces/srv/SimpleCommand "{data: 0}"

4. is_idle/audio — Проверка состояния аудиоплеера

Тип: robohead_interfaces/srv/SimpleCommand
Полное имя: /media_driver/is_idle/audio

В поле запроса ничего задавать не нужно - значение игнорируется.

Поля ответа

ПолеТипЗначение
dataint161 — плеер простаивает (idle, пауза, EOF)
0 — активное воспроизведение

Логика определения idle

Плеер считается idle, если выполняется любое условие:

  • idle-active = yes (ничего не загружено)
  • pause = yes (на паузе)
  • eof-reached = yes (конец файла)
  • path пуст (файл не загружен)

Пример

ros2 service call /media_driver/is_idle/audio robohead_interfaces/srv/SimpleCommand "{data: 0}"

5. is_idle/display — Проверка состояния видеоплеера

Тип: robohead_interfaces/srv/SimpleCommand
Полное имя: /media_driver/is_idle/display

В поле запроса ничего задавать не нужно - значение игнорируется.

Поля ответа

ПолеТипЗначение
dataint161 — дисплей простаивает
0 — активное отображение

Пример

ros2 service call /media_driver/is_idle/display robohead_interfaces/srv/SimpleCommand "{data: 0}"

API: Топики

Подписка: stream — Потоковый вывод изображений

Тип: sensor_msgs/msg/Image
Полное имя: /media_driver/stream
QoS: глубина очереди 1, best_effort

Описание

Принимает ROS-изображения (формат bgr8) и отображает их на дисплее в реальном времени. Используется для вывода динамического контента (анимации лица, интерфейса).

Механизм работы

  1. Входящее изображение конвертируется в OpenCV Mat
  2. Кадр сохраняется в /dev/shm/robohead_stream_frame.ppm (RAM, без диска)
  3. Видеоплеер обновляет отображение через loadfile ... replace

Важно: Стриминг не влияет на аудиоплеер и не требует остановки текущего воспроизведения (оно остонавливается автоматически)

Пример

Запустите пакет usb_cam (появится топик /image_raw) - он получает данные с камеры и публикует их в топик

ros2 run usb_cam usb_cam_node_exe

В отдельном терминале перейдите в папку примеров пакета и запустите скрипт, пересылающий кадры из топика /image_raw в топик /media_driver/stream:

cd /home/pi/robohead_ws/src/robohead2/media_driver/examples/
python3 imager.py

Паблишер: touchscreen - публикация касаний тачскрина

Тип: robohead_interfaces/msg/TouchEvent
Полное имя: /media_driver/touchscreen
QoS: глубина очереди 10

Поля сообщения TouchEvent

ПолеТипОписание
slotintНомер multitouch-слота (из ABS_MT_SLOT)
tracking_idintИдентификатор касания (из ABS_MT_TRACKING_ID). На up остаётся последним валидным значением
xintКоордината X (после поворота), в диапазоне устройства
yintКоордината Y (после поворота), в диапазоне устройства
statestring"down", "move", "up"

Семантика событий

  • "down" публикуется в момент касания пальца тачскрина (когда для слота получен валидный tracking_id >= 0 и есть координаты x/y)
  • "move" публикуется если палец двигается по тачскрину (только если координаты реально изменились относительно предыдущего опубликованного положения)
  • "up" публикуется при отрыве пальца от тачскрина (когда приходит tracking_id = -1 для слота, слот удаляется из внутренней карты состояний)

Поворот

Узел читает диапазоны координат из драйвера (EVIOCGABS для ABS_MT_POSITION_X/Y, иначе ABS_X/Y) и использует их для вычисления width_/height_.

Параметр display_rotate задаётся в градусах (обычно 0/90/180/270).
Внутри узла применяется компенсация поворота:

  • берётся r = display_rotate % 360
  • затем фактический поворот тача делается как rotation_deg_ = (360 - r) % 360

То есть если экран повернут, тач автоматически “поворачивается обратно”, чтобы итоговые (x,y) совпадали с координатами отображения.

Поддерживаются углы 0/90/180/270 (быстрые ветки) и произвольные углы (через sin/cos с поворотом вокруг центра и clamp в диапазон).

Пример

Запустите пакет media_driver (плеер+тачскрин)

ros2 launch media_driver media_driver.launch.py

В отдельном терминале перейдите в папку примеров пакета и запустите скрипт:

cd /home/pi/robohead_ws/src/robohead2/media_driver/examples/
python3 touchscreen.py

При касании под пальцем будет появляться красный круг (down). При движении он сменит цвет на зеленый (move). При отрыве пальца от дисплея кружок станет синим (up). Если каснуться несколькими пальцами, то кружочков будет несколько. s0, s1,... - номера слотов (один слот - один палец). id56, id57 - уникальный номер касания: остается одним и тем же, пока палец не оторван от экрана, при повторном касании инкрементируется на единицу.


Параметры конфигурации

Файл: media_driver/config/media_driver.yaml

Имена сервисов

ПараметрТипПо умолчаниюОписание
srv_play_media_namestringplay_mediaИмя сервиса воспроизведения
srv_set_volume_namestringset_volumeИмя сервиса установки громкости
srv_get_volume_namestringget_volumeИмя сервиса получения громкости
srv_is_idle_audio_namestringis_idle/audioИмя сервиса проверки состояния аудио
srv_is_idle_display_namestringis_idle/displayИмя сервиса проверки состояния дисплея

Имена топиков

ПараметрТипПо умолчаниюОписание
topic_stream_namestringstreamИмя топика для приёма видеопотока
topic_touchscreen_namestringtouchscreenИмя топика для публикации касаний тачскрина

Настройки медиа

ПараметрТипПо умолчаниюОписание
display_rotatestring270Угол поворота дисплея (0, 90, 180, 270)
default_volumedouble50.0Начальная громкость аудиоплеера (0–100)
stop_commandstring__STOP__Команда для остановки плеера

Настройки тачскрина

ПараметрТипПо умолчаниюОписание
device_namestringwaveshareПодстрока для поиска устройства по имени (регистронезависимо). Сравнивается с EVIOCGNAME
device_pathstring/dev/input/Путь, где искать event*
display_rotatestring270Угол поворота дисплея (0, 90, 180, 270)

Дополнительные ресурсы