GStreamer基礎教程02 - 基本概念

nicholas_duan發表於2020-10-15

 

摘要

在 Gstreamer基礎教程01 - Hello World中,我們介紹瞭如何快速的通過一個字串建立一個簡單的pipeline。為了能夠更好的控制pipline中的element,我們需要單獨建立element,然後再構造pipeline,下面將介紹GStreamer的一些基本概念並展示pipeline的另一種構造方式。

 

基本概念

Element

我們知道element是構建GStreamer pipeline的基礎,element在框架中的型別為GstElement,所有GStreamer提供的解碼器(decoder),編碼器(encoder), 分離器(demuxer), 音視訊輸出裝置都是派生自GstElement。

那麼element到底是什麼呢?
從應用的角度,我們可以將一個element認為是一個功能塊,他實現一個特定的功能,比如:資料讀取,音訊解碼,聲音輸出等。各個功能塊之間可以通過某種特定的資料介面(這種介面稱為pad,將在後續章節講述)進行資料傳輸。每個element有唯一的型別,還有相應的屬性,用於控制element的行為。

 

為了更直觀的展現element,我們通常用一個框來表示一個element,在element內部使用小框表示pad。


這些功能塊有些可以生成資料,有些只接收資料,有些先接收資料,再生成資料。為了便於區分這些element,我們把他們分為三類:
1. source element
只能生成資料,不能接收資料的element稱為source element。例如用於檔案讀取的filesrc等。
對於source element,我們通常用src pad表示element能產生資料,並將其放在element的右邊。source element只有src pad,通過裝置、檔案、網路等方式讀取資料後,通過src pad向pipeline傳送資料,開始pipeline的處理流程。如下圖:

2. sink element
只能接收資料,不能產生資料的element稱為sink element。例如播放聲音的alsasink等。
對於sink element,我們通常用sink pad表示element能接收處理資料,並將其放在element的左邊。sink element只有sink pad,從sink pad讀取資料後,將資料傳送到指定裝置或位置,結束pipeline的處理流程。如下圖:

3. filter-like element
既能接收資料,又能生成資料的element稱為filter-like element。例如分離器,解碼器,音量控制器等。
對於filter-like element,既包含位於element左邊的sink pad,又包含位於element右邊的src pad。Element首先從sink pad讀取資料,然後對資料進行處理,最後在src pad產生新的資料。如下圖:

對於這些的element,可能包含多個src pad,也可能包含多個sink pad,例如mp4的demuxer(qtdemux)會將mp4檔案中的音訊和視訊的分離到audio src pad和video src pad。而mp4的muxer(mp4mux)則相反,會將audio sink pad和video sink pad的資料合併到一個src pad,再經其他element將資料寫入檔案或傳送到網路。demuxer如下圖:

 

連線element
當我們有很多element時,我們需要將他們按資料的傳輸路徑將其串聯起來,src pad只能聯接到sink pad,這樣才能夠實現相應的功能。

 

 

 

Bin和Pipeline

我們將element串聯起來後就能實現相應的功能,為什麼我們還需要bin和pipline呢?我們首先來看看在GStreamer框架中element,bin,pipeline物件之間的繼承關係:

複製程式碼

GObject
    ╰──GInitiallyUnowned
        ╰──GstObject
            ╰──GstElement
                ╰──GstBin
                    ╰──GstPipeline

複製程式碼

 

這裡bin和pipeline都是一個element,那麼bin和pipeline都在element的基礎上實現了什麼功能,解決了什麼問題呢?
我們在建立了element多個element後,我們需要對element進行狀態/資源管理,如果每次狀態改變時,都需要依次去操作每個element,這樣每次編寫一個應用都會有大量的重複工作,這時就有了bin。
Bin繼承自element後,實現了容器的功能,可以將多個element新增到bin,當操作bin時,bin會將相應的操作轉發到內部所有的element中,我們可以將bin認為認為是一個新的邏輯element,由bin來管理其內部element的狀態及資源,同事轉發其產生的訊息。常見的bin有decodebin,autovideoconvert等。

 

Bin實現了容器的功能,那pipeline又有什麼功能呢?
在多媒體應用中,音視訊同步是一個基本的功能,需要支援這樣的功能,所有的element必須要有一個相同的時鐘,這樣才能保證各個音訊和視訊在同一時間輸出。pipeline就會為其內部所有的element選擇一個相同的時鐘,同時還為應用提供了bus系統,用於訊息的接收。

 

Bus

剛才我們提到pipeline會提供一個bus,這個pipeline上所有的element都可以使用這個bus嚮應用程式傳送訊息。Bus主要是為了解決多執行緒之間訊息處理的問題。由於GStreamer內部可能會建立多個執行緒,如果沒有bus,應用程式可能同時收到從多個執行緒的訊息,如果應用程式在傳送執行緒中通過回撥去處理訊息,應用程式有可能阻塞播放執行緒,造成播放卡頓,死鎖等其他問題。為了解決這類問題,GStreamer通常是將多個執行緒的訊息傳送到bus系統,由應用程式從bus中取出訊息,然後進行處理。Bus在這裡扮演了訊息佇列的角色,通過bus解耦了GStreamer框架和應用程式對訊息的處理,降低了應用程式的複雜度。

 

Element Hello World

在有上面的知識後,我們通過一個示例來看看element是如何建立及使用的。

複製程式碼

#include <gst/gst.h>

int main (int argc, char *argv[])
{
  GstElement *pipeline, *source, *filter, *sink;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  /* Create the elements */
  source = gst_element_factory_make ("videotestsrc", "source");
  filter = gst_element_factory_make ("timeoverlay", "filter");
  sink = gst_element_factory_make ("autovideosink", "sink");

  /* Create the empty pipeline */
  pipeline = gst_pipeline_new ("test-pipeline");

  if (!pipeline || !source || !filter || !sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline */
  gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL);
  if (gst_element_link_many (source, filter, sink, NULL) != TRUE) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* Modify the source's properties */
  g_object_set (source, "pattern", 0, NULL);

  /* Start playing */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* Wait until error or EOS */
  bus = gst_element_get_bus (pipeline);
  msg =
      gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
      GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

  /* Parse message */
  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 ("End-Of-Stream reached.\n");
        break;
      default:
        /* We should not reach here because we only asked for ERRORs and EOS */
        g_printerr ("Unexpected message received.\n");
        break;
    }
    gst_message_unref (msg);
  }

  /* Free resources */
  gst_object_unref (bus);
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);
  return 0;
}

複製程式碼

編譯並執行示例,可以看到彈出的視窗中播放著測試視訊,並且還顯示著播放時間。

$ gcc basic-tutorial-2.c -o basic-tutorial-2 `pkg-config --cflags --libs gstreamer-1.0`
$ ./basic-tutorial-2

 

原始碼分析

建立Element

/* Create the elements */
  source = gst_element_factory_make ("videotestsrc", "source");
  filter = gst_element_factory_make ("timeoverlay", "filter");
  sink = gst_element_factory_make ("autovideosink", "sink");

在對GStreamer進行初始化後,我們可以通過gst_element_factory_make建立element。第一個引數是element的型別,可以通過這個字串,找到對應的型別,從而建立element物件。第二個引數指定了建立element的名字,當我們沒有儲存建立element的物件指標時,我們可以通過gst_bin_get_by_name從pipeline中取得該element的物件指標。如果第二個引數為NULL,則GStreamer內部會為該element自動生成一個唯一的名字。
我們在當前示例中建立了3個element:videotestsrc,timeoverlay,autovideosink,其作用分別為:

  • videotestsrc是一個source element,用於產生視訊資料,通常用於除錯。
  • timeoverlay是一個filter-like element,可以在視訊資料中疊加一個時間字串。
  • autovideosink上一個sink element,用於自動選擇視訊輸出裝置,建立視訊顯示視窗,並顯示其收到的資料。

 

建立Pipeline

 /* Create the empty pipeline */
  pipeline = gst_pipeline_new ("test-pipeline");

Pipeline通過gst_pipeline_new建立,引數為pipeline的名字。


我們知道pipeline會提供播放所必須的時鐘以及對訊息的處理,所以我們需要把我們建立的element新增到pipeline中。

複製程式碼

/* Build the pipeline */
gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL);
if (gst_element_link_many (source, filter, sink, NULL) != TRUE) {
  g_printerr ("Elements could not be linked.\n");
  gst_object_unref (pipeline);
  return -1;
}

複製程式碼

從上面的講解我們知道pipeline是繼承自bin,所以所有bin的方法都可以應用於pipeline,需要注意的是,我們需要通過相應的巨集(這裡是GST_BIN)來將子類轉換為父類,巨集內部會對其做型別檢查。在這裡我們使用gst_bin_add_many將多個element加入到pipeline中,這個函式接受任意多個引數,最後以NULL表示引數列表的結束。如果一次只需要加入一個,可以使用gst_bin_add函式。


在將element加入bin後,我們需要將其連線起來才能完成相應的功能,由於有多個element,所以我們這裡使用gst_element_link_many,element會根據引數的順序依次將element連線起來。

需要注意的是,只有被加入到同一個bin的element才能夠被連線在一起,所以我們需要在連線前,將所需要的element加入到pipeline/bin中。

 

至此,我們已經完成了pipeline的建立,test-pipeline可以表示為:

 

 設定element屬性

/* Modify the source's properties */
g_object_set (source, "pattern", 0, NULL);

大部分的element都有自己的屬性。有的屬性只能被讀取,這種屬性常用於查詢element的狀態。有的屬性同時支援修改,這種屬性常用於控制element的行為。


由於GstElement繼承於GObject,同時GObject物件系統提供了 g_object_get()用於讀取屬性,g_object_set()用於修改屬性,g_object_set()支援以NULL結束的屬性-值的鍵值對,所以可以一次修改element的多個屬性。

我們這裡通過g_object_set()來修改videotestsrc的pattern屬性。pattern屬性可以控制測試影像的型別,可以嘗試將0修改為1,檢視輸出結果有何不同。

我們可以通過gst-inspect-1.0 videotestsrc命令來檢視pattern所支援的所有值。

 

設定播放狀態

複製程式碼

  /* Start playing */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (pipeline);
    return -1;
  }

複製程式碼

在完成pipeline的建立以及屬性的修改後,我們將pipeline的狀態設定為PLAYING,這裡與上一文章中的示例相同,只增加來錯誤處理,其他的返回值處理講在後續章節講述。

 

等待播放結束與釋放資源

這部分內容與上一文章中的示例相同,這裡只增加了訊息型別的判斷。更多關於訊息的內容將在後續章節講述。
由於videotestsrc會持續輸出視訊資料,所以我們在這個例子中不會受到EOS訊息,只有當我們關閉視訊視窗時會收到error訊息,傳送訊息的element名和具體的訊息內容會通過終端輸出。

 

總結

在本教程中,我們掌握了:

  • GStreamer element,bin,pipeline,bus的基本概念。
  • 如何使用gst_element_factory_make ()建立element。
  • 如何使用gst_pipeline_new ()建立pipeline。
  • 如何使用gst_bin_add_many ()將多個element加入pipeline。
  • 如何使用gst_element_link_many ()將多個element連線起來。

在這兩篇文章中,我們介紹了pipeline的兩種建立方式,下一篇文章我們將介紹GStreamer是如何保證element可以正確的連線在一起。


引用

https://gstreamer.freedesktop.org/documentation/tutorials/basic/concepts.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/application-development/basics/elements.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/application-development/basics/bins.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/gstreamer/gstpipeline.html?gi-language=c

 

作者:John.Leng

出處:http://www.cnblogs.com/xleng/

本文版權歸作者所有,歡迎轉載。商業轉載請聯絡作者獲得授權,非商業轉載請在文章頁面明顯位置給出原文連線.

相關文章