Multimediální frameworky: použití knihovny GStreamer

5. 2. 2025
Doba čtení: 7 minut

Sdílet

Autor: Depositphotos
Ukážeme si použití knihovny GStreamer, která je postavena nad knihovnou GLib. Předvedeme si vytváření instancí elementů, jejich zařazení do pipeline, následnou správu smyčky zpráv a plugin dekódující video.

K překladu vlastní aplikace je vhodné využít nástroj pkg-config, který je schopen vrátit fragment příkazové řádky pro překlad (kompilaci) i sestavení (linkování) aplikace vůči daným knihovnám. K jednoduchým příkladům níže bude stačit použít pouze knihovnu gstreamer-1.0. Ve zdrojovém kódu je nejprve třeba vložit hlavičkový soubor  <gst/gst.h>.

Protože je GStreamer postaven nad knihovnou GLib, je vhodné vložit také <glib.h>. Dále je tedy možné používat také funkce z GLib. Než bude možné použít knihovny GStreamer, musí být na počátku funkce main zavolána funkce gst_init, která knihovnu inicializuje (načte registry). Níže je zdrojový kód triviální aplikace, která inicializuje knihovnu GStreamer a pomocí funkce g_print z knihovny GLib vypíše na standardní výstup text.

#include <gst/gst.h>
#include <glib.h>

int main(int argc, char *argv[])
{
    gst_init(&argc, &argv);

    g_print("Started...\n");

    return 0;
}

Nejdůležitějším datovým typem GStreameru je pravděpodobně GstElement . Jedná se o abstraktní třídu, ze které vycházejí všechny elementy v pipeline. Dokonce celá pipeline, což je třída GstPipeline , dědí z GstElement . Další důležitou třídou poděděnou zGstElement  je koš elementů GstBin . SamotnýGstElement dědí v důsledku ze třídyGObject knihovny GLib, což mu mj. poskytuje funkcionalitu počítání referencí (a automatické uvolňování paměti) a čtení/nastavování vlastností. Každý element (GstElement ) je vždy v jednom ze stavůGST_STATE_VOID_PENDING (žádný),GST_STATE_NULL (zastaven),GST_STATE_READY (připraven k pozastavení),GST_STATE_PAUSED (pozastaven) neboGST_STATE_PLAYING (spuštěn). Stavy lze zjišťovat a nastavovat pomocí funkcígst_element_get_state gst_element_set_state .

Pro snazší orientaci a odkazování je možné elementy pojmenovat. Název elementu může být získán pomocí funkce gst_element_get_name a nastaven pomocí gst_element_set_name. Všechny elementy mají pady (src nebo sink, vysvětleno v prvním díle seriálu), což jsou objekty typu GstPad. Pomocí padů se elementy vzájemně propojují. Spoji pak tečou data obalená typem GstBuffer. Odkaz na existující pad může být získán pomocí funkce gst_element_get_static_pad. Nový pad může být vytvořen buď s funkcí gst_element_request_pad (ze šablony) nebo gst_element_get_request_pad (podle názvu).

Ke spojení dvou elementů skrze pady slouží gst_element_link. K propojení řetězce mnoha elementů je rychlejší použít gst_element_link_many. Pokud je nutné omezit datové formáty spojení (capabilities), je k dispozici funkce gst_element_link_filtered, která mezi parametry obdrží také odkaz na objekt třídy GstCaps, která mediální formáty (např. video/x-raw-rgb) popisuje. Pokud je třeba propojovat přímo jednotlivé pady elementů, jsou k dispozici ještě funkce gst_element_link_padsgst_element_link_pads_filtered.

Elementy

K vytváření instancí elementů slouží funkce gst_element_factory_make, která jako parametr obdrží název šablony elementu (např. videotestsrc nebo ximagesink), ze které má být element instanciován. Druhým parametrem je požadovaný název elementu (může být NULL, pokud má unikátní název vygenerovat GStreamer). Funkce gst_element_factory_make je vlastně spojením dvou funkcí – gst_element_factory_find (vrátí šablonu elementů) a gst_element_factory_create (vytváří instance ze šablony). Šablona je typu  GstElementFactory.

Samotné elementy je výhodné umístit do pipeline ( GstPipeline). Ta poskytuje funkci hodin a správu sběrnice zpráv. K vytvoření nové pipeline slouží funkce  gst_pipeline_new.

Všechny vytvářené objekty je třeba uvolnit pomocí gst_object_unref nebo jiné specializované funkce. Zařazením elementů do pipeline budou tyto elementy uvolněny automaticky při destrukci samotné pipeline.

Následuje jednoduchý příklad, který zkonstruuje pipeline sestávající ze dvou propojených elementů videotestsrc a ximagesink. V příkladu je ukázána aplikace, která vytvoří pipeline a vloží do ní dva elementy. Pipeline je následně spuštěna (přepnuta do stavu GST_STATE_PLAYING). Výpustkou je naznačeno čekání nebo smyčka zpráv. Následně se pipeline zastaví a uvolní. Ošetření chyb bylo pro stručnost vypuštěno.

int main(int argc, char *argv[])
{
    gst_init(&argc, &argv);

    GstElement *pipeline = gst_pipeline_new("pipeline");
    GstElement *source = gst_element_factory_make("videotestsrc",
        "source");
    GstElement *sink   = gst_element_factory_make("ximagesink",
        "sink");

    gst_bin_add_many(GST_BIN(pipeline), source, sink, NULL);
    gst_element_link(source, sink);
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // ...

    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(pipeline));

    return 0;
}

Smyčka zpráv

Správu smyčky zpráv poskytuje knihovna GLib. Smyčka samotná je zastřešena datovým typem GMainLoop . O vytvoření nové smyčky se postará funkce g_main_loop_new , ke spuštění slouží g_main_loop_run , k ukončeníg_main_loop_quit a k uvolnění prostředků g_main_loop_unref . Při přehrávání je možné použít tuto smyčku k čekání na zprávu o konci datového toku (GST_MESSAGE_EOS ). Následně se aplikace může ukončit.

Zprávy je možné přijímat pomocí sběrnice zpráv, kterou poskytuje samotná pipeline (třída GstPipeline ). Sběrnice je typuGstBus a vlastní zprávy jsou pak GstMessage . K získání odkazu na sběrnici lze využít funkci gst_pipeline_get_bus . K přidání vlastní callback funkce na sběrnici slouží gst_bus_add_watch . Callback funkce slouží k asynchronnímu chytání zpráv ve vytvořené smyčce zpráv (výše).

Zkrácený příklad je uveden níže. Je vidět zkrácená aplikace, která vytvoří pipeline, přidá na její sběrnici callback funkci a odstartuje smyčku zpráv. Tímto způsobem lze počkat na konec datového toku a ukončit přehrávač.

static GMainLoop *loop;

static gboolean bus_callback(GstBus *bus, GstMessage *message,
    gpointer data)
{
    if(GST_MESSAGE_TYPE(message) == GST_MESSAGE_EOS)
        g_main_loop_quit(loop);
    return TRUE;
}

int main(int argc, char *argv[])
{
    gst_init(&argc, &argv);

    GstElement *pipeline = gst_pipeline_new("pipeline");
    GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
    gst_bus_add_watch(bus, bus_callback, NULL);
    gst_object_unref(bus);

    // ...

    loop = g_main_loop_new(NULL, FALSE);
    g_main_loop_run(loop);

    // ...
}

Videokodek

Pro vytvoření prvního pluginu GStreameru je vhodné postupovat podle příručky „Plugin Writer's Guide“ v dokumentaci. Zde bude demonstrován plugin dekódující video. API pro tvorbu pluginů se od API pro tvorbu aplikací liší (je širší).

Pro vysvětlení je třeba nejdříve prohloubit znalosti z předchozích sekcí. Připojné body elementů, tzv. pady, mají vždy jeden ze dvou směrů – source nebo sink. Pad směru sink (cílový) je uvnitř pluginu zdrojem dat. Do src (zdrojový pad) jsou pak elementem generována nová data. Z vnějšího podledu tečou data vždy ze src do sink padu. Pady mají dále tři dostupnosti (availabilities) – vždy (always), někdy (sometimes) a na požádání (on request).

Pady s dostupností „vždy“ existují jednoduše vždy. Pady s dostupností „někdy“ jsou pluginem vytvořeny, pokud je k tomu důvod (např. ve vstupním kontejneru se objeví příslušná datová stopa). Pady s dostupností „na požádání“ jsou vytvořeny, pokud jsou požadovány (např. další výstup elementu „tee“, který větví vstupní datový tok). Formáty dat definují, jaká data mohou padem protékat. V případě dekodéru videa to na vstupním padu bude nově zavedený typ MIME video/x-root, na výstupním pak video/x-raw-bgr, k čemuž slouží makro GST_VIDEO_CAPS_BGR. Pro přidání nového typu MIME pro FourCC nalezený uvnitř kontejneru RIFF (AVI) je třeba upravit balíček gst-plugins-base-libs.

Nejjednodušší cestou k implementaci dekodéru videa bude pravděpodobně specializace třídy GstVideoDecoder, která dědí ze třídy  GstElement.

Pady elementu se vytvářejí z tzv. šablon. K definici této šablony slouží makro GST_STATIC_PAD_TEMPLATE, které vytvoří objekt typu GstStaticPadTemplate. Dekodér bude mít jeden pad směru sink a jeden směru src.

static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE(
    "sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS("video/x-root"));

static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE(
    "src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS(GST_VIDEO_CAPS_BGR));

K vlastní definici nového elementu slouží příkaz make_element . Dále lze implementovat jednotlivé funkce.

Třída GstVideoDecoder dovoluje překrýt následující virtuální metody.

  • start (volitelně): Volána na počátku zpracování dat. Vhodné místo pro alokaci prostředků.
  • stop (volitelně): Volána při ukončení zpracování. Vhodné místo pro uvolnění prostředků.
  • scan_for_sync (volitelně): Čeká na synchronizační bod mezi snímky.
  • parse_data (požadováno při vstupu nerozčleněném do paketů): Rozčleňuje příchozí data do snímků.
  • set_format: Upozorňuje element na nový datový formát na vstupním padu.
  • reset (volitelně): Reset po změně pozice v datovém toku (seek).
  • finish (volitelně): Volána pro vyprodukování zbývajících dat (např. při konci datového toku).
  • handle_frame: Dekódování jednoho snímku.

Funkce překrývající výše uvedené virtuální metody budou mít stejný postfix jako název překrývané funkce. Funkce s postfixem_base_init má za úkol přidat pady a a vyplnit detaily jako je název elementu, jeho třída (zde Codec/Decoder/Video ) a popis. Funkce s postfixem_init inicializuje vlastní instanci elementu.

Zde je důležité nastavit položkupacketized třídyGstVideoDecoder na TRUE , což zajistí zpracování vstupních dat rovnou na snímcích. Ke zpracování snímku slouží funkce s postfixem _handle_frame . Uvnitř je třeba nejprve alokovat pomocígst_video_decoder_get_frame nový buffer pro výstupní (dekomprimovaný) snímek.

Odeslání dat do výstupního padu nakonec zajistí gst_video_decoder_finish_frame . Poslední nutnou funkcí dekodéru je ta s postfixem _start . Zde je vhodný moment pro zjištění rozměrů obrazu ze vstupního padu. K tomu lze využít funkci gst_pad_get_negotiated_caps . Je však nutné ošetřit stav, kdy ještě není na vstupu napojen element dodávající data.

Dále je třeba zaobalit element dekodéru do pluginu. K tomu slouží makro GST_PLUGIN_DEFINE, kterému se předá odkaz na vstupní funkci pluginu. V té je nutné zavolat  gst_element_register.

Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Autor vystudoval Fakultu informačních technologií VUT v Brně, kde nyní pracuje jako vědecký pracovník. Zajímá se o multimédia a na svých strojích používá výhradně Gentoo Linux.