07-06-02 Минимальный пример Python RTSP — окно
Теперь, когда вы познакомились с gst-launch-1.0 и научились собирать пайплайны из командной строки, настало время перейти к следующему уровню — управлению GStreamer через код. В реальной разработке медиаприложений (например, систем видеонаблюдения, мостов протоколов или плагинов для OBS) редко используются только команды в терминале. Гораздо чаще GStreamer интегрируется в приложения на C, Python или других языках.
В этом разделе мы разберём минимальный, но рабочий пример на Python, который делает то же самое, что и команда gst-launch: подключается к RTSP-камере и отображает видео в окне. Но теперь — не как строка в терминале, а как полноценная программа, которую можно модифицировать, расширять и встраивать в другие системы.
Инициализация GStreamer в Python
Прежде чем использовать любые функции GStreamer, необходимо инициализировать библиотеку. Это обязательный шаг, аналогичный запуску движка.
import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject
Gst.init(None)
Что происходит здесь:
gi— это Python-обёртка для GObject Introspection, механизм, позволяющий использовать C-библиотеки (включая GStreamer) из Python.gi.require_version("Gst", "1.0")— указывает, что мы хотим использовать версию GStreamer 1.0.Gst.init(None)— инициализирует GStreamer. ПараметрNoneозначает, что мы не передаём аргументы командной строки (в отличие от C-версии).
💡 Аналог в C:
В C-коде это выглядело бы какgst_init(&argc, &argv);.
В Python мы просто вызываемGst.init(), и движок готов к работе.
Создание пайплайна из строки
Один из самых простых способов начать — использовать строковое описание пайплайна, как в gst-launch, но уже внутри Python-кода. Это даёт плавный переход от CLI к программированию.
pipeline_str = """
rtspsrc location=rtsp://user:pass@ip/stream latency=0 !
decodebin !
videoconvert !
autovideosink
"""
Разбор строки:
Этот пайплайн:
rtspsrc— подключается к RTSP-потоку по указанному URL.decodebin— автоматически выбирает подходящий декодер (например, H.264 → RAW).videoconvert— преобразует формат видео под нужды следующего элемента (например, из I420 в RGB).autovideosink— выводит видео в окно (через X11, Wayland и т.п.).
🔍 Важно: строка должна быть корректной с точки зрения синтаксиса GStreamer.
Пробелы, точки с запятой и отступы — всё это имеет значение.
Теперь передаём строку в GStreamer:
pipeline = Gst.parse_launch(pipeline_str)
Что делает Gst.parse_launch()?
Эта функция парсит строку и строит из неё объект пайплайна — точно так же, как это делает gst-launch-1.0. Внутри создаются и соединяются все элементы, как если бы мы делали это вручную.
✅ Плюс подхода:
Вы можете сначала отладить пайплайн в терминале, а потом просто скопировать его в Python.
Это отличный способ начать, не погружаясь сразу в сложности API.
Запуск пайплайна: управление состояниями
После создания пайплайн находится в состоянии NULL — ничего не делает. Чтобы начать воспроизведение, нужно перевести его в состояние PLAYING.
pipeline.set_state(Gst.State.PLAYING)
Состояния пайплайна (напоминание из лекции 07-02-04):
| Состояние | Описание |
|---|---|
NULL | Изначальное состояние, элементы не инициализированы. |
READY | Элементы готовы, но не работают. |
PAUSED | Пайплайн "заморожен" — буферы могут заполняться, но вывод не идёт. |
PLAYING | Пайплайн активно обрабатывает данные. |
⚠️ Важно: переход между состояниями не мгновенный.
GStreamer может тратить время на установку соединения, получение SPS/PPS, синхронизацию.
Поэтому сразу послеset_state(PLAYING)пайплайн может ещё не работать — это нормально.
Обработка событий: шина (bus) и сообщения
Теперь пайплайн работает, но у нас нет способа узнать, что происходит. А вдруг ошибка? Или поток закончился? Чтобы отслеживать такие события, используется шина (bus) — центральная очередь сообщений.
bus = pipeline.get_bus()
bus — это как «почтовый ящик» пайплайна. Все элементы могут отправлять туда сообщения: об ошибках, завершении, изменениях состояния и т.д.
Чтение сообщений с таймаутом
Мы будем периодически проверять шину на наличие важных событий:
while True:
msg = bus.timed_pop_filtered(
10 * Gst.MSECOND,
Gst.MessageType.ERROR | Gst.MessageType.EOS
)
if msg:
if msg.type == Gst.MessageType.ERROR:
err, debug = msg.parse_error()
print("ERROR:", err, debug)
break
elif msg.type == Gst.MessageType.EOS:
print("End of stream")
break
Как это работает:
timed_pop_filtered(timeout, types)— ждёт сообщение не дольше заданного времени (здесь — 10 мс), но только определённых типов.- Мы интересуемся двумя типами:
ERROR— произошла ошибка (например, камера недоступна).EOS(End of Stream) — поток завершён (например, камера отключилась).
- Если пришло сообщение — обрабатываем его и выходим из цикла.
🔄 Почему цикл
while True?
Это простая замена полноценного event loop. В реальных приложениях (например, с GUI) используетсяGObject.MainLoop, но здесь мы хотим сохранить пример минимальным.
Корректное завершение: освобождение ресурсов
После завершения работы (по ошибке или по EOS) необходимо остановить пайплайн и освободить ресурсы.
pipeline.set_state(Gst.State.NULL)
Почему NULL?
Перевод в состояние NULL:
- Останавливает все элементы.
- Освобождает память, закрывает сетевые соединения, уничтожает окна.
- Это обязательный шаг при завершении работы с GStreamer.
❌ Что будет, если не вызвать
set_state(NULL)?
Программа может "зависнуть" или оставить открытые соединения.
В некоторых случаях окно не закроется, или камера останется занятой.
Полный код — с комментариями
Вот как выглядит весь пример целиком, готовый к запуску:
import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject
# Инициализация GStreamer
Gst.init(None)
# Описание пайплайна — как в gst-launch
pipeline_str = """
rtspsrc location=rtsp://user:pass@192.168.1.100:554/stream latency=0 !
decodebin !
videoconvert !
autovideosink
"""
# Создание пайплайна из строки
pipeline = Gst.parse_launch(pipeline_str)
# Запуск пайплайна
pipeline.set_state(Gst.State.PLAYING)
# Получение шины для отслеживания событий
bus = pipeline.get_bus()
print("Пайплайн запущен. Для выхода — ожидание ошибки или EOS...")
# Цикл обработки сообщений
try:
while True:
# Ждём сообщение до 10 мс
msg = bus.timed_pop_filtered(
10 * Gst.MSECOND,
Gst.MessageType.ERROR | Gst.MessageType.EOS
)
if msg:
if msg.type == Gst.MessageType.ERROR:
err, debug = msg.parse_error()
print("❌ ОШИБКА:", err, "\nДетали:", debug)
break
elif msg.type == Gst.MessageType.EOS:
print("⏹️ Поток завершён (EOS)")
break
except KeyboardInterrupt:
print("\nПрервано пользователем")
# Остановка пайплайна
pipeline.set_state(Gst.State.NULL)
print("Пайплайн остановлен")
Как использовать этот код на практике
- Замените URL на реальный адрес вашей RTSP-камеры.
- Убедитесь, что GStreamer установлен и Python-биндинги работают:
python3 -c "import gi; gi.require_version('Gst', '1.0'); from gi.repository import Gst; print(Gst.version())" - Запустите скрипт:
python3 rtsp_player.py
Если всё настроено верно — вы увидите окно с видео с камеры.
Почему это важно: от строки к приложению
Сравните два подхода:
| Характеристика | gst-launch (CLI) | Python-код |
|---|---|---|
| Простота старта | ✅ Очень просто | 🟡 Нужно знать Python |
| Возможность обработки ошибок | ❌ Только вывод в терминал | ✅ Можно реагировать, переподключаться |
| Управление параметрами "на лету" | ❌ Только перезапуск | ✅ Можно менять latency, location и т.д. |
| Интеграция с GUI / веб-интерфейсом | ❌ Нет | ✅ Легко встраивается |
| Автоматизация | ❌ Через скрипты оболочки | ✅ Полноценная логика |
🎯 Вывод:
gst-launch— отличный инструмент для отладки и тестирования.
Но реальные приложения строятся на коде, где вы контролируете каждый аспект: подключение, ошибки, переключение камер, обработку кадров.
Дальнейшие шаги
После этого примера вы можете:
- Добавить обработку аудио (через
decodebin→audioconvert→autoaudiosink). - Внедрить переподключение при обрыве (по
ERRORилиEOS). - Использовать
appsink, чтобы получить кадры в OpenCV (следующий слайд). - Добавить GUI (например, с GTK или PyQt), чтобы управлять пайплайном кнопками.
Но главное — вы теперь понимаете: gst-launch — это не магия.
Это просто удобная обёртка над теми же API, которые вы только что использовали в Python.