
И снова здравствуй, хабраюзер, интересующийся фреймворком GStreamer. В прошлой статье было рассказано о том, как инициализировать библиотеку для полноценной работы с ней. А сегодня мы разберем процесс создания элементов и компоновки конвейера. В качестве практического материала будет создан
Создание элемента с помощью фабрики
Определение элемента доступно и с картинками изложено здесь, однако нужно дать некоторые пояснения. Установленные в системе наборы плагинов (а они, кстати, делятся на Core, Base, Good, Ugly, Bad и т.д. в зависимости от качества плагина и отсутствия/наличия проблем с лицензированием) определяют список фабрик для создания элементов. Давайте посмотрим, какие фабрики доступны для элементов типа Source.
#include <gst/gst.h> int main (int argc, char * argv[]) { /* Двунаправленный список, в который мы поместим список фабрик */ GList *list; /* Инициализация 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 <gst/gst.h> int main (int argc, char * argv[]) { 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 Manual
GStreamer 1.0 Core Reference Manual
GStreamer Core Plugins Reference
