Gstreamer
В этом курсе важнейшими инструментами, помимо OBS, для нас станут FFMPEG и GStreamer.
С FFMPEG те, кто изучали курс компьютерной графики, знакомы. Только там мы не работали с потоками, а здесь это будет его основное применение.
В работе с FFMPEG прекрасно то, что этот инструмент хорошо документирован и вы легко найдете решения для большинства задач в документации или в профильных форумах.
С gstreamer всё сложнее. Вряд ли вы легко загуглите что-то за пределами основ. И в этом курсе совсем глубоко мы погрузиться не сможем, освоим основные концепции и самое необходимое для работы с потоками.
Если FFMPEG -- это инструмент, которым можно легко пользоваться из командной строки и можно обходиться совсем без программирования, то GStreamer мы будем использовать из программ, которые вы будете писать на питоне.
Почему объединили эти два инструмента и чем нам не хватает уже знакомого FFMPEG?
Начнем с принципиальных отличий.
Одно я уже назвал: с GStreamer мы будем работать или из OBS, где он вызывается через плагин, или из программ, которые вы будете писать сами.
Второе -- это возможности, про них поподробнее.
- Непосредственно в курсе мы будем использовать способность gstreamer работать с потоками RTSP в реальном времени без буферизации или с предельно маленьким буфером -- в пределах сотни миллисекунд. Решения на основе FFMPEG менее стабильны и работают с кратно большими задержками -- на практике от 500 миллисекунд и более.
- Вторая возможность -- менять параметры выполнения программы во время ее исполнения. Это уже посложнее. Если для изменения параметров работы ффмпег нужно остановить и перезапустить его с новыми параметрами, то GStreamer можно дать другие параметры на ходу. Например, это можно использовать для ручного или автоматического переключения источника потока. Правда, для этого придется писать программу, а не запускать из командной строки или пакетного файла, как это делали с FFMPEG.
В основном мы будем использовать GStreamer для работы с потоками по протоколу RTSP. По нему отдают видео IP камеры и кодеры.
Что такое GStreamer
::: info GStreamer -- это мультимедийный фреймворк для создания потоковых медиа приложений.
:::
Его основа - это видеопайплайн. Одной из причин для начала его разработки в 1999 году была бедность мультимедиа в Linux. Кстати, многие мультимедийные проекты были начаты около 1999-2000 года, в том числе FFmpeg, что говорит о наличии проблем достаточно красноречиво. И, хотя GStreamer начинался как инструмент для создания видео и аудио проигрывателей, он хорош в задачах, где важна низкая задержка и высокая эффективность приложений, то есть, для трансляций. Сообщество вокруг этого фреймворка живо и спустя более 20 лет.
Зачем он нужен и где используется
::: info Список программ, которые используют GStreamer.
:::
Библиотеки с фреймворком GStreamer существуют под C, C++, Python, Ruby и т.д. Код в примерах написан на C, так как GStreamer ориентирован на концепции этого языка, да и написан тоже на нем. Для несложных задач возможно использовать и другие языки.
Эта статья частично является переводом уроков с официального сайта этого фреймворка, у некоторых из них есть варианты кода на python и javascript, при желании можете изучить и их.
Также существует отладочный инструмент gst-launch-1.0, который нежелательно использовать при сборке своих приложений. Понимание этого инструмента позволит лучше разобраться в том, что такое пайплайн и как его составлять, поможет отлаживать программы, а также пригодится для использовании плагина GStreamer для OBS.
Терминология
::: warn
В этой теме мы даже больше, чем обычно, будем сталкиваться с неустоявшейся русской терминологией.
:::
Это общая беда многих технических областей, где вся документация написана на английском, а в русской версии либо нет общепринятых терминов, либо они не менее иностранные. Например, в компьютерных сетях вас могут поправить, что вместо „свич“ надо говорить „коммутатор“, вместо „роутер“ -- „маршрутизатор“, вместо „фаерволл“ -- „брендмауэр.
Мы тоже будем заменять используемые в документации английские слова на чуть более понятные, но не менее английские. Например, „пэды“ будем переводить как „коннекторы“. Просто потому, что „PAD“ -- это не подушка, не блокнот и не коврик в нашем случае, а действительно место соединения элементов, коннектор.
Мы встречали эти термины в документах и в форумах и надеемся, что вам поможет знание этой терминологии. Тем не менее, старайтесь запоминать оригинальные термины, это поможет при поиске.
Пайплайн
::: info Пайплайн — это набор из готовых элементов, соединенных последовательно, где выход одного элемента является входом другого.
:::
Элементы являются базовыми „кирпичиками“, из которых строятся пайплайны в GStreamer. Они обрабатывают данные, которые исходят из элементов-источников и приходят в элементы-потребители (получатели), проходя через элементы фильтры. Чем элемент-источник отличается от фильтра или получателя? Это разделение происходит по наличию коннекторов (pads).
::: info Коннектор - это составляющая элемента, с помощью которых эти самые элементы соединяются. Коннекторы бывают двух видов:
:::
- входы, или потребители (sink), куда поступает информационный поток, и
- выходы, или источники (src), которые такой поток отдают.
У элементов-источников есть только выход -- коннектор-источник.
Такие элементы являются только источниками информационных потоков, на вход ничего не принимают.
У элементов-потребителей есть только вход - коннектор-получатель.
Такие элементы только принимают информацию, но из них ничего нельзя получить.
У элементов-фильтров есть вход и выход.
То есть, для предыдущего элемента в пайплайне он является приёмником, а для следующего - источником.

В итоге, элементы можно соединить таким образом:

или таким:

Примеры выше являются упрощенными по сравнению с реальными пайплайнами. Код и схему одного из них вы видите на ниже.
gst-launch-1.0
audiotestsrc ! voaacenc ! mux.
v4l2src device='/dev/video0' io-mode=2 ! image/jpeg, width=1280, height=720, framerate=30/1 !
nvjpegdec ! 'video/x-raw, width=1280, height=720, framerate=30/1' !
nvvidconv ! 'video/x-raw(memory:NVMM), width=1280, height=720, framerate=30/1' ! queue max-size-bytes=0 max-size-buffers=0 ! glue.
rtspsrc location='rtsp://<camera_ip>/Channel1 ' protocols=tcp latency=500 ! rtph264depay ! h264parse ! nvv4l2decoder !
nvvidconv left=420 right=1500 top=0 bottom=1080 ! 'video/x-raw(memory:NVMM), width=1920, height=1080, framerate=25/1' ! queue max-size-bytes=0 max-size-buffers=0 ! glue.
videotestsrc pattern=2 is-live=true ! autovideoconvert ! glue.
nvcompositor name=glue \\n sink_0::xpos=691 sink_0::ypos=195 sink_0::width=1220 sink_0::height=691
sink_1::xpos=0 sink_1::ypos=195 sink_1::width=691 sink_1::height=691
sink_2::xpos=0 sink_2::ypos=886 sink_2::width=1920 sink_2::height=194
background-w=1920 background-h=1080 background=0 !
nvvidconv ! 'video/x-raw(memory:NVMM), width=1920, height=1080, framerate=25/1' ! nvv4l2h264enc bitrate='4500000' !
h264parse config-interval=1 disable-passthrough=true ! queue max-size-bytes=0 max-size-buffers=0 ! mux.
flvmux name=mux streamable=true ! rtmpsink location='rtmp://a.rtmp.youtube.com/live2/<stream_key> live=1 flashver=FME/3.0\20(compatible;\20FMSc\201.0)' &> debug_strm_cam+screen.txt

При работе с GStreamer не менее важным умением, чем программировать на C, является искусство поиска в интернете.
Но вместо того, чтобы каждый раз искать информацию о нужном элементе, можно использовать команду gst-inspect-1.0. Используется она следующим образом:
gst-inspect-1.0 <название элемента>
В выводе данной команды можно увидеть, что делает данный элемент, узнать, какие у него коннекторы и свойства, которые ему можно задать и т.д.
Возможности - caps
У коннекторов есть capabilities (caps). Можно перевести как возможности или характеристики. Это тип данных, с которыми работает данный коннектор.
Рассмотрим на примере коннектора элемента x264enc, это кодировщик H264. У его коннектора-получателя возможности это video/x-raw, он ждет на вход сырой видеопоток, чтобы его закодировать. А возможности коннектора-источника - video/x-h264, то есть элемент, который стоит за ним, получит закодированный поток, так что, скорее всего, это будет мультиплексор, про который подробнее поговорим далее.
Если вы подадите на вход элементу неправильный тип данных, он не сможет их обработать и пайплайн не запустится.

Помимо коннекторов, у элементов есть свойства (properties), которые задаются через пробел после названия элемента в формате <название свойства>=<значение>.
Свойства можно посмотреть через команду gst-inspect-1.0.
Элементы-фильтры
Начнем с мультиплексоров и демультиплексоров (muxer/demuxer). Чтобы понять, что это за элементы и зачем они нужны, необходимо понимать, что такое медиаконтейнеры.
::: info Медиаконтейнер, это такая сущность, внутри которой есть:
метаданные: например, информация о кодеке, количество потоков в файле, размер, имя и дата создания файла, название контента, одним слов, данные о данных внутри контейнера, и
медиапотоки: то есть видео- и аудиодорожки, а иногда и субтитры.
:::
::: info Посмотрите это видео чтобы узнать больше о контейнерах и медиаформатах.
:::
Чтобы извлечь из контейнера эти медиапотоки нам пригодится демультиплексор. Аналогично, для „упаковки” данных в контейнер, необходимо использовать мультиплексор. Зачем нужно „открывать” контейнер? Почему нельзя работать с потоками напрямую? Поток, выходящий из файла, является одним целым, и, если захотите его как-то преобразовать, например, уменьшить размер кадра видео, то сделать у вас этого не получится.
На входе у такого элемента один коннектор, а на выходе столько, сколько дорожек было внутри контейнера(в примере две дорожки - для звука и для видео):
В этом случае при обработке пайплайна появляются две ветки:
одна - для обработки аудио, вторая - для обработки видео:

Вот как выглядит этот пайплайн при работе с gst-launch:
gst-launch-1.0 filesrc location=file://<path> ! oggdemux name=sep ! vorbisdec ! autoaudiosink sep. ! theoradec ! autovideosink
Здесь:
- демультиплексору дано имя через свойство
name. - Дальше идет часть пайплайна для ветки в аудио.
- Затем, после конца этой ветки, через пробел пишем имя с точкой.
- После точки можно написать название конкретного получателя. Продолжаем ветку для видео.
Кодирование видео
Пример кодирования видео
Из тестового источника выходит сырой поток и он кодируется кодеком H.264.
gst-launch-1.0 -v videotestsrc ! x264enc ! mp4mux ! filesink location=res.mp4
videotestsrcоткрывается тестовый потокx264encпоток сжимается кодеком H.264,mp4muxупаковывается в контейнер mp4,filesink location=res.mp4потребитель (sink), в свойствах -- путь к выходному файлу.
Пример декодирования видео
gst-launch-1.0 -v filesrc location=example.mkv ! matroskademux ! avdec_h264 ! ximagesink
(пример для Linux)
Упаковщики / распаковщики (pay/depay)
Элементы типа pay/depay чем-то схожи с (де)мультиплексорами, только они достают данные не из контейнеров, а из пакетов.
Например, rtph264depay достает видео, закодированное по стандарту H264 из rtp пакетов, которые используются, например, в протоколе RTSP при получении видео с камер или кодеров. Если захотите что-нибудь сделать с таким видео, его нужно будет декодировать перед обработкой.
Пример:
gst-launch-1.0 rtspsrc location="rtsp://<IP-адрес>/<путь>" ! rtph264depay ! h264parse ! rtspclientsink location="rtsp://<IP-адрес>/<путь>"
Парсеры
::: info Парсеры (parse) обрабатывают полученные данные и разделяют их на отдельные видео/аудио кадры и метаданные (пакетированные закодированные данные со временными отметками, где возможно) и добавляют необходимые для кодирования/декодирования данные.
:::
Пример:
gst-launch-1.0 rtspsrc location="rtsp://<IP-адрес>/<путь>" ! rtph264depay ! h264parse ! rtspclientsink location="rtsp://<IP-адрес>/<путь>"
Полезные элементы пайплайна
-
Элемент
playbin: если задать свойствоuri- это может быть файл, RTSP-поток или какое-нибудь видео, доступное по http, то он автоматически составит пайплайн из источников, декодеров, демультиплексоров и получателей для проигрывания этого медиа на экране.gst-launch-1.0 playbin uri=file://hehe.mkv -
Элемент
uridecodebinдекодирует данные из URI в сырое медиа. Он выбирает элемент-источник, который может „захватить“ данные по этому URI и соединяет его с элементом decodebin. Выступает в качестве демультиплексора, поэтому предлагает много коннекторов-источников, опираясь на потоки, которые найдет в медиа.gst-launch-1.0 uridecodebin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm ! videoconvert ! autovideosink -
Элемент
decodebinавтоматически создает пайплайн для декодирования, используя имеющиеся декодеры и демультиплексоры, пока не получится сырой медиапоток. Он используется внутриuridecodebin, который создает подходящий элемент-источник.gst-launch-1.0 souphttpsrc location=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm ! decodebin ! autovideosink -
Элемент
videoscaleпозволяет изменять разрешение видео:gst-launch filesrc location=videofile.mov ! decodebin ! videoscale ! video/x-raw-yuv,width=640,height=340 ! autovideosink -
Элемент
videocropобрезает видеокадр:gst-launch-1.0 -v videotestsrc ! videocrop top=42 left=1 right=4 bottom=0 ! ximagesink

-
Элемент
compositorпозволяет расположить несколько видео на одном полотне. пример:gst-launch-1.0 compositor name=comp \
sink_0::alpha=1 sink_0::xpos=0 sink_0::ypos=0 \
sink_1::alpha=0.5 sink_1::xpos=320 sink_1::ypos=0 ! \
queue2 ! decodebin ! video/x-raw, width=800, height=600 ! videoconvert ! xvimagesink \
videotestsrc pattern=1 ! "video/x-raw" ! comp.sink_0 \
filesrc location=tst.mp4 ! decodebin ! videoconvert ! comp.sink_1
Многопоточность
Очереди (queue)
Использование очередей (queue) позволяет распараллелить вычисления и буферизировать информацию в автоматическом режиме для передачи между элементами.
Очереди выполняют две задачи:
- Данные отправляются в очередь, пока она не достигнет выбранного предела.
Любая попытка добавить еще буферов в очередь блокирует процесс, который пытается это сделать, пока не появится свободное место. - Очередь создает новую нить процесса на коннекторе источника, чтобы разъединить обработку на коннекторах-получателях и на источниках.
Помимо этого, очередь отправляет сигналы о том, когда она становится пустой или полной (зависит от конфигурации) и может иметь инструкции, чтобы отбросить буферы вместо блокирования процессов при переполнении.
Пример:
-
Без очереди:
gst-launch-1.0 videotestsrc ! autovideosink
-
С очередью:
gst-launch-1.0 videotestsrc ! queue ! autovideosink

Очереди (queue2)
Queue2 не является улучшенной версией queue: исполняет те же задачи, что и queue, а также может хранить полученные данные (или их часть) в файле, для последующего восстановления. Также он замещает сигналы более общими и удобными сообщениями.
К сожалению, не всегда можно просто сказать, какой из вариантов лучше использовать.
::: success
Старайтесь использовать queue2, когда пайплайн обрабатывает
не сетевые потоки. Например, файл.
:::
::: success
Старайтесь использовать queue, когда пайплайн обрабатывает
сетевые потоки. Например, поток RTSP.
:::
Разветвители (tee)
Элемент tee (тройник) копирует поток, который в него приходит, и передает его в точку с соответствующим именем.
Пример использования: нужно скопировать видео и поместить копию рядом с оригиналом.

gst-launch-1.0 \
videotestsrc ! tee name=t \
t. ! queue ! videoconvert ! autovideosink \
t. ! queue ! videoconvert ! autovideosink