instrumentisto/medea

Research Janus and Kurento inner design

Opened this issue · 3 comments

Kurento design

Kurento построен на gstreamer и унаследовал весь дизайн от него. Кодовую базу Kurento можно разделить на три части:

  1. gstremer plugins
  2. higher level abstractions
  3. json-rpc API, свой сборщик мусора и прочий обвес не представляющий интереса

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

Сейчас нам интереснее абстракции высшего уровня. Для начала немного о внутреннем дизайне gstreamer:

  1. Element - an element is the most important class of objects in GStreamer. An element has one specific function, which can be the reading of data from a file, decoding of this data or outputting this data to your sound card (or anything else).
  2. Pad - pads are element's input and output, where you can connect other elements. For the most part, all data in GStreamer flows one way through a link between elements. Data flows out of one element through one or more source pads, and elements accept incoming data through one or more sink pads. Source and sink elements have only source and sink pads, respectively.
  3. Bin - a bin is a container for a collection of elements. Since bins are subclasses of elements themselves, you can mostly control a bin as if it were an element, thereby abstracting away a lot of complexity for your application.
  4. Pipeline - a pipeline is a top-level bin. It provides a bus for the application and manages the synchronization for its children. As you set it to PAUSED or PLAYING state, data flow will start and media processing will take place. Once started, pipelines will run in a separate thread until you stop them or the end of the data stream is reached.

image

Все нагло скопировано отсюда

По похожей философии построены и внешние абстракции Kurento, единственное, что они достаточно удачно прячут все low-level details. У Kurento точно так-же есть базовый MediaElement с функцией connect(), но уровень абстракции его наследников значительно выше чем у элементов gstremer. Kurento MediaElement можно воспринимать как gstreamer bin.

Иерархия абстракций верхнего уровня:

mediaendpointdiagram

В итоге, получается достаточно удобное API. Но, главная фишка - сокрытие всех необходимых транскодирований/трансмуксирований. Соединяя два элемента, пользователь не переживает о совместимости их pad'ов:

One of the big problems of media is that the number of variants of video and audio codecs, formats and variants quickly creates high complexity in heterogeneous applications. So Kurento developed the concept of an automatic converter of media formats that enables development of agnostic elements. Whenever a media element’s source is connected to another media element’s sink, the Kurento framework verifies if media adaption and transcoding is necessary and, if needed, it transparently incorporates the appropriate transformations making possible the chaining of the two elements into the resulting Pipeline.

Считаю это решении достаточно удачным и предлагаю рассмотреть его или в качестве внешнего API или в качестве одного из внутренних слоев медиа-сервера.

Janus

Inner design

Внутренности можно разделить на 3 слоя:

  1. transports
  2. plugins
  3. protocols

Transports - транспорты внешнего API: http, websockets, mqtt, nanomsg, pfunix, rabbitmq.
Связывает пользовательские сессии с plugin handle.

Plugins - весь функционал завернут в ряд плагинов, например:

  1. janus.plugin.echotest - делает loopback, отправляет медиа на сервер и возвращает обратно пользователю.
  2. janus.plugin.videocall - 1-to-1 call.
  3. janus.plugin.videoroom - n-to-n call.

Плагины дергают протоколы.

Protocols - реализации используемых протоколов уровня медиа/транспорта медиа, часть базируется на соответсвующих либах: dtls(на OpenSSL, libsrtp), ice( на libnice, 4к строчная оберточка получилась 😄 ), rtcp, rtp, sctp(на libusrsctp), sdp.

Внутренности устроены весьма симпатично, но, в нашем случае, учитывая что мы хотим применять абстракции gstreamer, не спускаясь(в идеале) на уровень реализаций протоколов, такой подход неприменим.

Outer API

There are basically three types/levels of endpoints you can meet:

  1. The server root (/janus by default, but configurable), which you only POST to in order to create a Janus session;
  2. The session endpoint (e.g., /janus/12345678, using the identifier retrieved with a previous create), which you either send a GET to (long poll for events and messages from plugins) or a POST (to create plugin handles or manipulate the session);
  3. The plugin handle endpoint (e.g., /janus/12345678/98765432, appending the handle identifier to the session one) which you only send POST messages to (messages/negotiations for a plugin, handle manipulation), as all events related to this handle would be received in the session endpoint GET (the janus.js library would redirect the incoming messages to the right handle internally).

Фактически, взаимодейтвие выглдядит следующим образом:

  1. Пользователь запросил создание сессии. REQ /root; RESP session_id&available_plugins.
  2. Пользователь запросил использование плагина. REQ /root/session_id?plugin_name; RESP plugin_handle_id
  3. Пользователь взаимодейтсвет с плагином. REQ /root/session_id/plugin_handle_id.

Очевидно, что плагины полностью независимы друг от друга.

В итоге, получается достаточно интересная архитектура. Единственное что меня смущает - сложности в избегании дублирования логики/концептов от плагина к плагину. Ожидаю, что этого можно было бы избежать путем создания дополнительной прослойки между протоколами и плагинами. Стоит подумать над возможностью реализации такой-же модульной архитектуры, но с учетом наших задач.

@alexlapa well... I see these two architectures are pretty same.

Janus plugins are literally pre-configured high-level Kurento pipelines, while Janus protocols are Kurento elements. And Janus transports are GStreamer/Kurento elements which has no sink and just produce the sources.
The difference is that Janus transport "invokes" Janus plugin by its handle, while Kurento pipeline has everything baked in. But such compounding seems to be the result of outer API design choices.

Also, I kinda dislike Janus outer API design, as it introduces too many round-trips.

@tyranron ,

Janus plugins are literally pre-configured high-level Kurento pipelines

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

while Janus protocols are Kurento elements

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

while Kurento pipeline has everything baked in.

Не сказал бы. Пользователь API сам собирает Pipeline. В комнату добавился новый получатель видео - мы создаем ему endpoint, подключаем этот endpoint к endpoint'у публикующего.

Я бы выделил два основных различия:

  1. У Kurento есть абстракции медиа-уровня - Kurento elements. Которые дергают gst-elements, которые можно назвать Janus-protocols. Это удобно.
  2. В Janus есть готовые приложения - плагины. Когда Kurento все перекладывает на дополнительные "room management" сервисы(openvidu, kurento-room).

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

К внутренней архитектуре еще вернемся. Сейчас будем формализировать внешнее контрольное API #4 , #5