Как разработать приложения видеоконференцсвязи на основе Gstreamer для приставок RDK и Linux

Пандемия COVID-19 стала катализатором появления новых онлайн-сервисов. Например, Zoom стал настолько успешным, что в этом месяце обогнал IBM по капитализации. Программные инженеры Promwad были вдохновлены этим успехом и решили пойти еще дальше: как насчет реализации видеоконференцсвязи на Smart TV и STB? Тогда у пользователей такого приложения будет возможность не только общаться на работе, но и наслаждаться удаленными встречами с друзьями, болеть за футбольную команду, вместе смотреть фильм или заниматься спортом с тренером.

Почему-то у большинства операторов цифрового ТВ нет такой услуги, хотя с инженерной точки зрения все эти функции могут быть реализованы на приставках на базе Linux/Android и RDK.

Давайте проанализируем архитектуру Zoom-подобного приложения для видеоконференцсвязи для Smart TV и реализуем кодирование видеопотока с помощью мультимедийной платформы с открытым исходным кодом  GStreamer. Мы собирали информацию для работы с этим фреймворком по частям, но оно того стоило.

Проблемы и архитектура программного обеспечения

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

Итак, что нас ждет на приставках:

  1. Ограниченные ресурсы процессора и самих устройств. В большинстве случаев устройства STB используют различные процессоры ARM, что подразумевает несколько ограничений и дополнительных задач, таких как необходимость использования аппаратного кодирования/декодирования для видеопотока. Итак, производительность – “узкое место”.
  2. Разные архитектуры в приставках разных производителей. Некоторые из них основаны на Android; другие используют дистрибутивы на основе RDK или Linux с их ограничениями и нюансами. Поэтому на старте процесса разработки лучше выбрать наиболее распространенные и кроссплатформенные решения в разных программных модулях. Не говоря уже о поддержке десктоп-версии. А затем перейдем к частным случаям.
  3. Сетевые ограничения. Многие приставки работают как через Ethernet, так и через Wi-Fi. Сжатие и передача видео/аудиопотоков – еще одно “узкое место” в приложениях такого типа.
  4. Безопасность потоковой передачи и другие вопросы безопасности данных.
  5. Поддержка камер и микрофонов на встраиваемых платформах.

Теперь мы можем кластеризовать саму архитектуру. Наше приложение для видеоконференцсвязи для приставок будет состоять из нескольких крупных компонентов и модулей:

  • Захват видеопотока
  • Захват аудиопотока
  • Сетевой модуль
  • Модуль кодирования видео / аудиопотока
  • Модуль декодирования видео / аудиопотока
  • Отображение видеоконференции на экране
  • Вывод звука
  • Цветовые преобразования
  • Несколько других второстепенных компонентов

В упрощенном виде архитектуру можно изобразить следующим образом:

Архитектура приложения видеоконференцсвязи для Smart TV

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

Кодирование и декодирование аудио/видеопотока

Преимущества GStreamer для видеоконференцсвязи


Как мы уже отмечали, потоковое видео – одно из “узких мест”. Предположим, у вас есть камера, которая выводит кадры со скоростью 30 кадров в секунду при небольшом разрешении 640 × 480. В сумме получается в RGB24: 640 * 480 * 3 * 30 = 27 648 000 байт в секунду, то есть более 26 мегабайт в секунду, что не работает по очевидным причинам, в частности, по пропускной способности сети.

Одно из решений – реализовать кодирование видео с помощью какой-нибудь библиотеки. Как можно догадаться из названия обзора, наш выбор пал на фреймворк GStreamer. Почему именно эта библиотека? Вот некоторые из его преимуществ перед другими решениями:

  1. Хорошее кроссплатформенное решение с отличной поддержкой Linux и Android.
  2. В RDK Gstreamer – это стандарт кодирования/декодирования, включенный в дистрибутив по умолчанию.
  3. Он поддерживает широкий спектр модулей, фильтров и кодеков. Например, FFmpeg, который можно использовать для тех же целей, является одним из модулей GStreamer.
  4. Легко построить конвейер. Легко создать цепочку кодирования/декодирования, а конвейерный подход позволяет плавно заменять кодеки, фильтры и т. д. без необходимости переписывать код.
  5. C/C++ API включен.
  6. Поддержка аппаратных кодеров/декодеров, в частности OpenMAX API  – важная вещь для работы с приставками.

Изучение GStreamer и конвейеров


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

  • gst-inspect-1.0 позволит вам увидеть список доступных кодеков и модулей, чтобы вы могли сразу увидеть, что с ним делать, и выбрать набор фильтров и кодеков.
  • gst-launch-1.0 позволяет запускать любой конвейер.

GStreamer использует схему декодирования, при которой поток последовательно проходит через различные компоненты, от источника к выходу приемника. Вы можете выбрать что угодно в качестве источника: файл, устройство, выход (приемник), также может быть файл, экран, сетевые выходы и протоколы (например, RTP).

Классический пример воспроизведения файла mp4:

Вход принимает файл mp4, который проходит через демультиплексор mp4 – qtdemux, затем через синтаксический анализатор h264, затем через декодер, конвертер и, наконец, вывод.

Вы можете заменить autovideosink на filesink с параметром файла и вывести декодированный поток непосредственно в файл.

Программирование приложения с помощью GStreamer C/C ++ API. Попробуем расшифровать.


Теперь, когда мы знаем, как использовать gst-launch-1.0, мы делаем то же самое в нашем приложении. Принцип остается прежним: мы строим конвейер декодирования, но теперь мы используем библиотеку GStreamer и glib-events.

Мы рассмотрим живой пример декодирования H264.

Инициализация приложения GStreamer происходит один раз с помощью

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

Примечание: независимо от того, сколько конвейеров у вас в приложении, достаточно один раз инициализировать gst_init.

Давайте создадим новый цикл событий, в котором будут обрабатываться события:

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

Каждый элемент конвейера создается через gst_element_factory_make, где первый параметр – это тип, а второй – его условное имя для GStreamer, на которое он позже будет полагаться (например, при выдаче ошибок).

Также было бы неплохо проверить, что все компоненты найдены, иначе gst_element_factory_make вернет NULL.

Мы устанавливаем тот же параметр местоположения через g_object_set:

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

Теперь нам нужен обработчик сообщений GStreamer, давайте создадим соответствующий bus_call:

gst_object_unref и другие подобные вызовы необходимы для очистки выбранных объектов.

Затем назовем сам обработчик сообщений:

А теперь самое главное: мы собираем и складываем все созданные элементы в единый конвейер, построенный через gst-launch. Конечно, важен порядок добавления:

Также стоит отметить, что такое связывание элементов отлично работает для потокового вывода, но в случае воспроизведения (автосвязка) требует дополнительной синхронизации и динамического связывания демультиплексора и парсера:

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

И, наконец, превратим статус конвейера в воспроизведение:

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

После этой процедуры нужно очистить все:

Выбор кодеров и декодеров. Запасные варианты.


В документации можно подробнее рассказать о полезных, но редко упоминаемых вещах: как легко организовать резервный декодер или кодировщик.

В этом нам поможет функция gst_element_factory_find, проверив, есть ли у нас кодек в фабрике элементов:

В этом примере мы отдали приоритет выбору аппаратного декодера OMX на платформе RDK, а в случае его отсутствия выберем программную реализацию.

Еще одна чрезвычайно полезная, но еще более редко используемая функция – это проверить, что мы фактически инициализировали в GstElement (какой из многих кодеков):

Вы можете сделать это таким простым способом и вернуть имя инициализированного кодека.

Цветовые модели видео


Нельзя не упомянуть и цветовые модели, поскольку речь идет о кодировании видео с камер. И тогда на сцену выходит YUV (гораздо чаще, чем RGB).

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

Некоторые кодировщики могут работать и с другими цветовыми моделями, но чаще всего это исключения из правил.

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

Давайте разберемся с буферами и будем получать данные на лету

Входной буфер


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

Теперь мы будем работать с буферами и входами и выходами appsrc/appsink. Почему-то в официальной документации этот вопрос практически не рассматривался.

Так как же организовать постоянный поток данных в созданных конвейерах, а если точнее – в выходной буфер и получить закодированный или декодированный выходной буфер? Допустим, мы получили изображение с камеры и нам нужно его закодировать. Мы уже решили, что нам нужен кадр в формате I420. Допустим, у нас есть, что дальше? Как пропустить картинку через весь поток конвейера?

Во-первых, давайте настроим обработчик события need-data, который будет запускаться, когда необходимо передать данные в конвейер и начать загрузку входного буфера:

Сам обработчик имеет следующий вид:

Можно сказать, что «изображение» – это псевдокод нашего буфера изображений в I420.

Далее через gst_buffer_new_allocate создаем буфер необходимого размера, который будет соответствовать размеру буфера изображения.

С помощью gst_buffer_map мы устанавливаем буфер в режим записи и используем memcpy для копирования нашего изображения в созданный буфер.

И наконец, мы сигнализируем GStream, что буфер готов.

Примечание: важно использовать gst_buffer_unmap после записи и очищать буфер после использования gst_buffer_unref. Иначе будет утечка памяти. В небольшом количестве доступных примеров никто особо не беспокоился об использовании памяти, хотя это очень важно.

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

Это делается перед установкой обработчика сигнала need-data:

Как и все параметры GstElement, параметры устанавливаются через g_object_set.

В данном случае мы определили тип потока – формат данных. Мы указываем, что вывод appsrc будет получать данные I420 с разрешением 640 × 480 и 30 кадрами в секунду.

Частота в нашем случае, да и вообще, никакой роли не играет. Во время работы мы не заметили, что GStreamer каким-то образом ограничивает вызовы необходимых данных по частоте.

Готово, теперь наши кадры загружены в кодировщик.

Выходной буфер


Теперь давайте узнаем, как получить закодированный выходной поток.

Подключаем обработчик к устройству вывода:

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

Обратный вызов имеет аналогичную структуру. Теперь нам нужно добраться до буферной памяти. Сначала мы получаем GstBuffer, затем указатель его памяти с использованием gst_buffer_get_memory по индексу 0 (как правило, он единственный задействован). Наконец, используя gst_memory_map, мы получаем адрес буфера данных bufInfo.data и его размер bufInfo.size.

Фактически, мы достигли цели – получить буфер с закодированными данными и их размером. Итак, мы рассмотрели ключевые и наиболее интересные компоненты нашего Zoom-подобного приложения для видеоконференцсвязи для Smart TV и телевизионных приставок: архитектура, модули кодирования/декодирования с GStreamer, буферы ввода/вывода и используемые преобразования цвета.

Для операторов цифрового телевидения такая программная платформа может стать новой абонентской услугой. А для нас, инженеров, это новый интересный встраиваемый проект для реализации различных приставок на базе RDK, Linux и Android. Что касается всех остальных, это возможность вместе провести время, смотреть фильмы и спортивные матчи, заниматься спортом и встречаться с близкими во время карантина COVID-19 или удаленной работы.

Эта идея с услугой видеоконференцсвязи на Smart TV может получить дальнейшее развитие, как с точки зрения инженерных решений, так и с точки зрения бизнес-сценариев. Так что не стесняйтесь делиться своими мыслями в комментариях ниже.

Выражаем свою благодарность источнику из которого взята и переведена статья, сайту cnx-software.com.

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

5 2 votes
Article Rating
Подписаться
Уведомление о
guest

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

1 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
radiolis
4 лет назад

“…хотя с инженерной точки зрения все эти функции могут быть реализованы на приставках на базе Linux/Android и RDK.”
На Андроид приставку можно поставить Скайт и вести видеоконференцию.