Знакомство с GStreamer: элементы и контейнеры
И снова здравствуй, хабраюзер, интересующийся фреймворком GStreamer. В прошлой статье было рассказано о том, как инициализировать библиотеку для полноценной работы с ней. А сегодня мы разберем процесс создания элементов и компоновки конвейера. В качестве практического материала будет создан аудиоплеер простенький копир файлов (вроде cp) — да-да, GStreamer настолько суров, что им можно чуть ли не пиво открывать. Итак, вперед!
Создание элемента с помощью фабрикиОпределение элемента доступно и с картинками изложено здесь, однако нужно дать некоторые пояснения. Установленные в системе наборы плагинов (а они, кстати, делятся на Core, Base, Good, Ugly, Bad и т.д. в зависимости от качества плагина и отсутствия/наличия проблем с лицензированием) определяют список фабрик для создания элементов. Давайте посмотрим, какие фабрики доступны для элементов типа Source.
#include
/* Инициализация GStreamer */ gst_init (NULL, NULL);
list = gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_SRC, GST_RANK_NONE);
GList *l; for (l = list; l!= NULL; l = l→next) g_print (»%s\n», gst_object_get_name (l→data));
gst_plugin_feature_list_free (list); gst_plugin_feature_list_free (l); return 0; } Результат: pulsesrc alsasrc dataurisrc filesrc jackaudiosrc rtmpsrc … Как видно, источников довольно много (просмотреть список всех фабрик можно, использовав макро GST_ELEMENT_FACTORY_TYPE_ANY). Обратите внимание на filesrc — его мы используем в практической части.Итак, для создания элемента мы ищем фабрику с нужным названием (например, filesrc):
GstElementFactory *factory; factory = gst_element_factory_find («filesrc»); А затем непосредственно создаем элемент, дав ему имя. По этому имени потом можно будет обращаться к элементу, и еще это удобно при отладке. GstElement *element; element = gst_element_factory_create (factory, «elname»); Для этих двух функций существует шорткат: GstElement *gst_element_factory_make (const gchar *factoryname, const gchar *name); У каждого созданного элемента есть набор свойств, которыми можно управлять и, таким образом, настраивать элемент. Задание и чтение свойств выполняются set- и get-методами: void g_object_set (gpointer object, const gchar *first_property_name, …); void g_object_get (gpointer object, const gchar *first_property_name, …); Для примера зададим элементу свойство «name»: g_object_set (G_OBJECT (element), «name», «another_name», NULL); Пять копеек о четырех состояниях Все созданные элементы могут находиться в одном из четырех состояний: GST_STATE_NULLВ это состояние элемент переходит сразу после его создания. GST_STATE_READYВ этом состоянии для элемента выделяются необходимые ресурсы, таким образом он подготавливается для перехода в состояние GST_STATE_PAUSED. GST_STATE_PAUSEDВ этом состоянии элемент полностью открыт для потока данных, но данные еще не передаются. GST_STATE_PLAYINGЭто состояние полностью идентично предыдущему, но при этом данные передаются. Переключать состояния элемента можно с помощью функции: GstStateChangeReturn gst_element_set_state (GstElement *element, GstState state); Стоит отметить, что переключения могут быть сквозными. Т.е. если элемент, находящийся в состоянии NULL, переключить в PLAYING, он автоматически пройдет через все промежуточные состояния.Особые элементы — контейнеры и конвейеры Теперь представьте, что у вас есть набор из десяти элементов, и вы хотите каждый элемент переключить, например, в состояние PLAYING. Было бы нелепо, если бы для этого пришлось 10 раз вызывать функцию gst_element_set_state (). Существует особый элемент, в который можно помещать другие элементы — контейнер (bin). Поместив в контейнер несколько элементов, можно управлять ими, как единым целым, например переключить состояние.Не нужно думать, что контейнер — это нечто обособленное. Нет, это такой же элемент экосистемы GStreamer, как и любой другой элемент. Значит и создать его можно с помощью фабрики:
GstElement *bin; bin = gst_element_factory_make («bin», «bin_name»); Также для этой операции есть вспомогательная функция: GstElement *gst_bin_new (const gchar *name); Для управления синхронизацией и обработки сообщений с шин (о шинах и сообщениях поговорим в следующий раз) выделяют контейнер верхнего уровня — конвейер (pipeline). В любом приложении, использующем контейнеры, должен присутствовать хотя бы один конвейер.Создается конвейер либо с помощью фабрики (фабрика «pipeline»), либо вспомогательной функцией:
GstElement *gst_pipeline_new (const gchar *name); Добавление элементов в контейнер и связывание Добавить элементы в контейнер (или конвейер) или удалить их оттуда можно функциями: gboolean gst_bin_add (GstBin *bin, GstElement *element); void gst_bin_add_many (GstBin *bin, GstElement *element_1, …, NULL); gboolean gst_bin_remove (GstBin *bin, GstElement *element); Каждый созданный элемент имеет т.н. пэды (pad) — точки, через которые можно связать элемент с другими элементами и, таким образом, создать рабочий медиа-конвейер. Эта концепция — ядро GStreamer.Связывание осуществляется функциями:
gboolean gst_element_link (GstElement *src, GstElement *dest); gboolean gst_element_link_many (GstElement *element_1, GstElement *element_2, …, NULL); Не все пэды совместимы друг с другом. Поэтому автоматически перед связыванием элементов происходит процесс проверки на совместимость.Нельзя забывать, что перед связыванием элементов они должны быть добавлены в конвейер. Также при добавлении элементов в конвейер, в котором уже находятся связанные элементы, их связи исчезают.
Практика Для закрепления теоретического материала напишем приложение, выполняющее копирование из файла в файл. Для этого мы будем использовать два элемента из набора Core — filesrc и filesink. Наш конвейер схематически будет выглядеть так:
Итак, поехали!
#include
if (argc!= 3) { g_print («Syntax error\n»); return -1; }
GstElement *pipeline, *src, *dst; /* Сюда будет читаться результат попытки запуска потока. */ GstStateChangeReturn ret; /* bus — это шина конвейера. Через нее мы можем получать сообщения о событиях. */ GstBus *bus; GstMessage *msg;
/* Инициализация GStreamer */ gst_init (NULL, NULL);
/* Создаем элементы */ pipeline = gst_element_factory_make («pipeline», «pipe»); src = gst_element_factory_make («filesrc», «src»); dst = gst_element_factory_make («filesink», «dst»); if (! pipeline || ! src || ! dst) { g_printerr («Unable to create some elements\n»); return -1; }
/* Добавляем элементы в конвейер */ gst_bin_add_many (GST_BIN (pipeline), src, dst, NULL);
/* И связываем их */ if (gst_element_link (src, dst) != TRUE) { g_printerr («Elements can not be linked\n»); gst_object_unref (pipeline); return -1; }
/* Задаем элементам свойства */ g_object_set (src, «location», argv[1], NULL); g_object_set (dst, «location», argv[2], NULL);
/* Запускаем конвейер */ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr («Unable to set pipeline to the playing state\n»); gst_object_unref (pipeline); return -1; }
/* Мало просто установить режим PLAYING. Нужно ждать либо конца потока, либо * ошибок. Для начала подключаемся к шине конвейера (эти манипуляции будут * описаны в следующей статье) */ bus = gst_element_get_bus (pipeline);
/* И ожидаем события на шине. Когда событие произойдет, функция вернет * сообщение, которое мы будем парсить. */ msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Парсим сообщение */ if (msg!= NULL) { GError *err; gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_ERROR: gst_message_parse_error (msg, &err, &debug_info); g_printerr («Error received from element %s: %s\n», GST_OBJECT_NAME (msg→src), err→message); g_printerr («Debugging information: %s\n», debug_info? debug_info: «none»); g_clear_error (&err); g_free (debug_info); break;
case GST_MESSAGE_EOS: g_print («We reach End-Of-Stream\n»); break;
default: g_printerr («Unexpected message received\n»); break; } gst_message_unref (msg); }
/* Освобождаем ресурсы */ gst_object_unref (bus); gst_element_set_state (pipeline, GST_STATE_NULL); gst_object_unref (pipeline);
return 0; } Компилируем и запускаем: $ gcc -Wall -o cp cp.c $(pkg-config --cflags --libs gstreamer-1.0) $ echo 'hello world' > file.txt $ ./cp file.txt another_file.txt We reach End-Of-Stream $ cat another_file.txt hello world Заключение В следующей статье будут рассмотрены шины и различные виды сообщений, которые по ней передаются. А для закрепления напишем приложение для любителей попеть караоке! Материалы по теме GStreamer Application Development ManualGStreamer 1.0 Core Reference ManualGStreamer Core Plugins Reference