И снова здравствуй, читатель, интересующийся фреймворком GStreamer. В прошлой статье было рассказано о том, как инициализировать библиотеку для полноценной работы с ней. А сегодня мы разберем процесс создания элементов и компоновки конвейера. В качестве практического материала будет создан аудиоплеер простенький копир файлов (вроде cp) — да-да, 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 ("%sn", 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 errorn");
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 elementsn");
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 linkedn");
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 staten");
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: %sn", GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %sn", debug_info ? debug_info : "none");
g_clear_error (&err);
g_free (debug_info);
break;
case GST_MESSAGE_EOS:
g_print ("We reach End-Of-Streamn");
break;
default:
g_printerr ("Unexpected message receivedn");
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
Автор: mike_patton