Pull to refresh

Знакомство с GStreamer: элементы и контейнеры

Reading time 6 min
Views 8.5K
image

И снова здравствуй, хабраюзер, интересующийся фреймворком 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 ("%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. Наш конвейер схематически будет выглядеть так:

image

Итак, поехали!

#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
Tags:
Hubs:
+9
Comments 0
Comments Leave a comment

Articles