Как достать соседа или Караоке на GStreamer

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

    image

    Шина


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

    Каждый конвейер по умолчанию имеет встроенную шину, т.е. ее не нужно создавать или настраивать. Шина извлекается из конвейера следующей функцией:

    GstBus * gst_pipeline_get_bus (GstPipeline *pipeline)
    

    Есть два способа обработки сообщений на шине — асинхронный и синхронный. Первый способ заключается в том, что к шине подключается обработчик, который по приходу сообщения выполняет какие-то действия. Делается это с помощью функций:

    guint gst_bus_add_watch (GstBus *bus, GstBusFunc func, gpointer user_data)
    void gst_bus_add_signal_watch (GstBus *bus)
    

    Стоит отметить, что обе эти функции имеют расширенные варианты (*_full), с помощью которых возможна более тонкая настройка обработчика.
    Второй способ — это постоянный опрос шины на предмет сообщений. Делается это при помощи функции:

    GstMessage *gst_bus_poll (GstBus *bus, GstMessageType events, GstClockTime timeout)
    

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

    Сообщения


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

    • Ошибки, warning'и и информационные сообщения
    • Сообщения о конце потока (End of Stream)
    • Теги
    • Изменения состояний

    Можно подойти к вопросу об обработке сообщений с другой стороны, а именно подключиться только к интересующим нас сигналам в следующей форме:

    g_signal_connect (bus, "message::error", G_CALLBACK (cb_message_error), NULL)
    

    Важно! Если вы хотите, чтобы обработчик продолжил реагировать на сообщения, он должен возвращать значение TRUE. Если же прописать возврат FALSE, то после обработки отслеживание сообщений этим обработчиком прекратится.

    Практика


    А сейчас напишем приложение, которое воспроизводит mp3-файл и подавляет в нем определенный диапазон частот (в который входит вокальный диапазон) с помощью элемента audiokaraoke. При этом на выходе мы получаем трек без вокала. Также в консоль выводится текст песни из файла (в кодировке UTF-8).

    Конвейер будет выглядеть следующим образом:

    image

    Убедитесь, что у вас установлен набор плагинов Ugly (gst-plugins-ugly), поскольку в его состав входит mp3-декодер Mad.

    А вот и сама программа:
    #include <gst/gst.h>
    
    #define SUPRESS_LEVEL 1.0   // от 0.0 до 1.0
    #define MONO_LEVEL    1.0   // от 0.0 до 1.0
    #define FILTER_BAND   220.0 // от 0.0 до 441.0
    #define FILTER_WIDTH  100.0 // от 0.0 до 100.0
    
    static GMainLoop *loop;
    
    static gboolean my_bus_callback (GstBus *bus, GstMessage *message, gpointer data)
    {
    	switch (GST_MESSAGE_TYPE (message)) {
    		case GST_MESSAGE_ERROR: {
    			GError *err;
    			gchar *debug;
    			gst_message_parse_error (message, &err, &debug);
    			g_print ("Error: %s\n", err->message);
    			g_error_free (err);
    			g_free (debug);
    			g_main_loop_quit (loop);
    			break;
    		}
    
    		case GST_MESSAGE_EOS: {
    			g_print ("\n\nYou've got 100 points!\n");
    			g_main_loop_quit (loop);
    			break;
    		}
    
    		default:
    			break;
    	}
    
    	/* возвращаем TRUE, поскольку мы хотим, чтобы обработчик продолжал выполнение своих обязанностей */
    	return TRUE;
    }
    
    int main (int argc, char * argv[])
    {
    	if (argc != 3)
    	{
    		g_printerr("Usage: %s audiofile.mp3 lyricsfile.txt\n", argv[0]);
    		return -1;
    	}
    
    	GstElement *pipeline, *audiosrc, *parser, *decoder, *converter, *karaoke, *audiosink;
    	GstElement *textsrc, *textsink;
    	GstStateChangeReturn ret;
    	
    	GstBus *bus;
    	guint bus_watch_id;
    
    	/* Инициализация */
    	gst_init(NULL, NULL);
    
    	/* Создаем элементы */
    	pipeline = gst_element_factory_make ("pipeline", "pipe");
    	audiosrc  = gst_element_factory_make ("filesrc", "audiosrc");
    	parser  = gst_element_factory_make ("mpegaudioparse", "parser");
    	decoder  = gst_element_factory_make ("mad", "decoder");
    	converter  = gst_element_factory_make ("audioconvert", "converter");
    	karaoke  = gst_element_factory_make ("audiokaraoke", "karaoke");
    	audiosink = gst_element_factory_make ("autoaudiosink", "audiosink");
    	
    	textsrc = gst_element_factory_make ("filesrc", "textsrc");
    	textsink = gst_element_factory_make ("fdsink", "textsink");
    
    	if (!pipeline || !audiosrc || !parser || !decoder || !converter || !karaoke || !audiosink || !textsrc || !textsink)
    	{
    		g_printerr ("Unable to create some elements\n");
    		return -1;
    	}
    
    	/* Добавляем элементы в конвейер */
    	gst_bin_add_many (GST_BIN(pipeline), audiosrc, parser, decoder, converter, karaoke, audiosink, textsrc, textsink, NULL);
    
    	/* и связываем их */
    	if (gst_element_link_many (audiosrc, parser, decoder, converter, karaoke, audiosink, NULL) != TRUE)
    	{
    		g_printerr ("Unable to link some elements\n");
    		gst_object_unref(pipeline);
    		return -1;
    	}
    	if (gst_element_link (textsrc, textsink) != TRUE)
    	{
    		g_printerr ("Unable to link text with textsink\n");
    		gst_object_unref(pipeline);
    		return -1;
    	}
    
    	/* Задаем элементам свойства */
    	g_object_set (audiosrc, "location", argv[1], NULL);
    	g_object_set (textsrc, "location", argv[2], NULL);
    	g_object_set (karaoke, "level", SUPRESS_LEVEL, "mono-level", MONO_LEVEL,
                                                  "filter-band", FILTER_BAND, "filter-width", FILTER_WIDTH, 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;
    	}
    
    	/* Извлекаем шину из конвейера и подключаем к ней обработчик */
    	bus = gst_element_get_bus (pipeline);
    	bus_watch_id = gst_bus_add_watch (bus, my_bus_callback, NULL);
    	gst_object_unref (bus);
    
    	/* Запускаем основной поток приложения */
    	loop = g_main_loop_new (NULL, FALSE);
    	g_main_loop_run (loop);
    
    	/* Освобождаем ресурсы */
    	gst_element_set_state (pipeline, GST_STATE_NULL);
    	gst_object_unref (pipeline);
    	g_source_remove (bus_watch_id);
    	g_main_loop_unref (loop);
    
    	return 0;
    }
    

    Компилируем как обычно
    $ gcc -Wall -o karaoke karaoke.c $(pkg-config --libs --cflags gstreamer-1.0)
    

    и запускаем
    $ ./karaoke audiofile.mp3 lyricsfile.txt
    

    Результаты


    Я пробовал запускать приложение (с настройками фильтра по умолчанию) на следующих песнях:
    Песня Эффект
    Tool — Parabola Неплохо вырезан вокал, но подавило много инструментала (особенно флажолеты в соло)
    At the Drive-In — Rolodex Propaganda Инструментал сохранен хорошо, но достаточно сильно пробивается вокал (видимо, ввиду его высоты)
    Radiohead — Jigsaw Falling into Place Здесь получилось довольно интересно — основной вокал задавлен, а бэк остался
    U2 — Raised by Wolves Гитара сохранена практически идеально

    Понятно, что результат сильно зависит от настроек фильтра и от самой песни, поэтому можно поиграть с его параметрами, указанными через #define и посмотреть, что из этого выйдет.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 3

      +1
      Пишу, т.к. это вторая ваша статья без комментариев, продолжайте, пожалуйста.
      По фреймворку очень мало инфы, сам года полтора им занимаюсь.
        +1
        Как же хорошо что вы продолжили то, что я начал!
        Сам бы продолжал, но ушел от аудио, и переключился на видеоконтент… :(
          0
          здорово

          Only users with full accounts can post comments. Log in, please.