首頁 > 軟體

C語言多媒體框架GStreamer使用教學深講

2022-07-19 18:02:29

之前參與的萬能視訊播放器專案採用了多媒體GStreamer開源框架,在最上層業務層通過連線各個外掛形成一個pipeline來完成相應的業務需求。但之前沒接觸過GStreamer框架,所以從專案的前期預研開始,就從GStreamer最基本的概念開始熟悉,逐步解決專案中遇到的多個問題。本文根據此次專案實踐,對GStreamer多媒體框架做一個相對全面的總結。

1、GStreamer簡介

GStreamer是GNOME桌面環境下用來建立串流媒體應用的多媒體框架,其基本設計思想來自於俄勒岡(Oregon)研究生學院有關視訊管道的創意,同時也借鑑了DirectShow的設計思想。

GStreamer是用c語言實現的,使用了物件導向的思維。GStreamer框架是基於外掛和管道的,所有的外掛都能夠被連結到任意的已經定義的資料流管道中,資料通過管道機制進行統一交換。GStreamer的很多優點來源於其框架的模組化,使得新的外掛能夠無縫合並。

GStreamer能夠處理任意型別的資料流,其目標是要簡化音/視訊應用程式的開發,其最顯著的用途是在構建音視訊播放器、編輯音視訊檔、音視訊格式轉換和串流媒體服務上,GStreamer已經能夠被用來處理像 MP3、Ogg、MPEG1、MPEG2、AVI、Quicktime 等多種格式的多媒體資料。

GStreamer核心庫函數是一個處理外掛、資料流和媒體操作的框架。另外,其還提供了一套API,用於程式設計師使用其它外掛來編寫他所需要的應用程式時使用。但是,由於追求模組化和高效率,使得GStreamer在整個框架上變的複雜,也同時因為複雜度的提高,使得開發一個新的應用程式顯得不是那麼的簡單。

2、GStreamer基本概念

2.1、元件(Element)

元件是GStreamer的核心,是具有一定功能的基本單元,可將其描述為一個具有特定屬性的黑盒子。其在程式碼裡面的型別是GstElement,可以理解為Gstreamer裡面的基礎類別。Gstreamer預設安裝了很多有用的元件,按照功能上的差異,element分為以下幾類:

(1)source element 資料來源元件,只有輸出端,用來產生供管道消費的資料,例如,音訊捕捉單元,它從音效卡讀取原始音訊資料,供其它模組用;

(2)filter(/filter-like) element 中間元件,包括過濾器、轉換器、複用器、解複用器、編解碼器等,其既有輸入端又有輸出端,從輸入端獲得相應資料,經過處理之後傳遞給輸出端,有的element可能有一個source pad多個sink pads(demux),有的可能有多個source pads一個sink pad(mux),有的有一個source pad一個sink pad,例如,音訊編碼單元,它從外界獲得音訊資料之後,根據壓縮演演算法編碼後,給其它模組使用;

(3)sink elements 接收器元件,只有輸入端,僅有消費資料的能力,是整條媒體管道的終端,例如,音訊回放單元,負責將接收到的資料寫到音效卡上;

2.2、箱櫃(Bin)

由多個基本單元組成的一個高階的功能單元,是裝載元件的容器,可以通過改變一個Bin的狀態來改變其內部所有元件的狀態,Bin可以傳送匯流排訊息(bus message)給其子集元件。

Bin和pipeline的區別就是pipeline肯定是bin,但bin不一定是pipeline,bin就像一個盒子,裡面放了什麼東西,功能具體是怎麼實現的,使用者可以不關心,bin是元件的集合,而pipeline更強調應用的可執行性。

2.3、管道(Pipeline)

最高等級的Bin,是一種允許對所包含的元件進行安排(scheduling)的普通容器。頂層(toplevel)箱櫃必須為一個管道,因此每個GStreamer應用程式都至少需要一個管道。當應用程式啟動後,管道會自動執行在後臺執行緒中,下面是一個典型的pipeline範例:

2.4、襯墊(Pad)

不同Elements之間的連結點,資料流在元件之間流動就是依靠Pads。Pads有處理特殊資料的能力,也就是其支援特定媒體型別的能力,一個Pads能夠限制資料流型別的通過,連結成功的條件是,兩個Pads允許通過的資料型別一致時才能建立(資料型別協商)。

Pads按照資料導向,可分為source pads(element的輸出),sink pads(element 的輸入),按照時效性可分為,永久型(always)、隨機型(sometimes)、請求型(on request),三種時效性的意義顧名思義: 永久型的襯墊一直會存在,隨機型的襯墊只在某種特定的條件下才存在(會隨機消失的襯墊也屬於隨機型),請求型的襯墊只在應用程式明確發出請求時才出現。

Pads通過GstCaps物件進行描述,一個GstCaps物件包括一個或者多個GstStructure物件,一個GstStructure描述一種媒體型別,其結構中只包含功能集中規定的固定值。

2.5、能力集(Caps)

Pad的屬性描述,例如:

  SRC template: 'src'
    Availability: Always
    Capabilities:
      audio/x-raw-float
                   rate: [ 8000, 50000 ]
               channels: [ 1, 2 ]
             endianness: 1234
                  width: 32
          buffer-frames: 0
  SINK template: 'sink'
    Availability: Always
    Capabilities:
      audio/x-vorbis

2.6、幽靈pad(ghost pad)

bin本身沒有pad,所以就沒有辦法把兩個bin連結起來。但可以用bin中的一個元件的pad構造一個代理pad,這樣bin就有一個代理pad了。這個pad實際指向被代理的那個單元的pad,範例如下:

2.7、Bus

Bus採用自己的執行緒機制,負責pipeline執行緒和應用程式程式之間的通訊。每個pipeline預設建立一個Bus,應用程式在匯流排上設定一個類似於物件的訊號處理的訊息處理器,當主迴圈執行的時候,匯流排將會輪詢這個訊息處理器是否有新的訊息,當訊息被採集到後,匯流排將呼叫相應的回撥函數來完成相關操作。

應用程式有兩種方法使用Bus,第一種是使用 GLib/Gtk+ main loop及gst_bus_add_watch () or gst_bus_add_signal_watch()事件回撥函數機制,第二種是程式通過gst_bus_peek () /gst_bus_poll ()主動檢查Bus中的訊息;

2.8、緩衝區(Buffer)

管道的資料流由一組緩衝區和事件組成,緩衝區包括實際的管道資料,事件包括控制資訊,如尋找資訊和流的終止訊號。所有這些資料流在執行的時候自動的流過管道。

緩衝區包含了你建立的管道里的資料流,通常一個源元件會建立一個新的緩衝區,同時元件還將會把緩衝區的資料傳遞給下一個元件。一個緩衝區主要由以下一個組成:

(1)指向某塊記憶體的指標;

(2)記憶體的大小;

(3)緩衝區的時間戳;

(4)一個參照計數,指出了緩衝區所使用的元件數。沒有元件可參照的時候,這個參照將用於銷燬緩衝區。

buffer的建立有2種方式,一種是由當前的element自己建立,然後把這個buffer傳遞給下一個element;另外一種方式就是dwonstream-allocated buffers,就是由下一個element來建立要求大小的buffer,並提供buffer操作函數,當前element通過呼叫buffer操作函數將資料寫入這個buffer中完成buffer資料傳遞。其區別在於buffer的建立是在資料傳輸的源端element建立還是在資料接收端element來建立。

2.9、外掛(Plugin)

元件必須封裝在外掛中才能被使用,一個外掛是一塊可以載入的程式碼,通常被稱為共用物件檔案(shared object file)或動態連結庫(dynamically linked library),一個外掛中可以包含一個或若干element。

3、GStreamer基本架構

GStreamer core、Plugins以及依賴的第三方開源庫的架構關係,如下圖所示,

Gstreamer的組成結構如下圖所示:

4、GStreamer通訊機制

Gstreamer的通訊機制示意圖及解釋如下:

4.1、Message

pipeline用來主動向外報告自己的執行狀態。這些Message被傳送到一個訊息佇列,也就是pipeline的Bus,應用程式可以從Bus中獲取Message,並作出自定義的反應。Message是GST提供的,屬於非同步操作;

4.2、Event

pipeline中外掛之間進行通訊的機制,分為下行事件,上行事件和雙向事件。也可以由應用程式直接向某一個外掛傳送事件,但起作用的前提是:該外掛定義了該事件的響應操作。 通過事件可以控制整個pipeline的執行狀態。

下行事件是由source外掛向sink外掛方向傳輸,例如,

GST_EVENT_EOS (流的終止訊號)

GST_EVENT_NEWSEGMENT

上行事件是由sink外掛向source外掛方向傳輸,用於改變管道中資料流的狀態,例如:

GST_EVENT_QOS

GST_EVENT_SEEK(查詢)

雙向事件,例如:

GST_EVENT_FLUSH_START

GST_EVENT_FLUSH_STOP

4.3、Signal

應用程式控制某一外掛的執行狀態,signal可以看做Glib物件的一個屬性,是由Gobject系統提供的,屬於同步操作,與linux中的系統訊號有差別。通過訊號可以讓某個外掛做一些對外掛本身變數的操作,比如增加或者刪除一些維護資訊等等。

4.4、Probe

應用程式可以通過探針Probe來探測某個外掛的pad中流過的資料,比如:在audioconert 外掛的src pad 加一個探針,每當有buf到達時,就呼叫callback_have_data(),這裡這個函數只是列印一下buf的大小,統計一下buf流過的個數;

//main
GstPad *m_pad_concert_src = gst_element_get_static_pad(m_gst_convert, "src");   
gst_pad_add_buffer_probe(m_pad_concert_src, G_CALLBACK(callback_have_data), NULL);
gst_object_unref(m_pad_concert_src);
 
/*******Callback handler when probe date received***********/
static gboolean callback_have_data(GstPad *padsrc, GstBuffer *buffer, gpointer data)
{
    gint    iBufSize = 0;
    gchar*     pBuffer = NULL;
    iBufSize = GST_BUFFER_SIZE(buffer);
    pBuffer = (gchar*)GST_BUFFER_DATA(buffer);
    static gint numBuf = 0;
    g_print("rBUF %d  Size=%d   ", numBuf++, iBufSize);
    return TRUE;
}

4.5、Quary

應用程式可以查詢pipline當前的執行狀態,比如:以下程式碼用來查詢當前播放的位置,和總的播放時間。

GstFormat m_format = GST_FORMAT_TIME;
gint64 m_position , m_length;
if( gst_element_query_position(pipeline, &m_format,&m_position) &&
  gst_element_query_duration(pipeline, &m_format, &m_length))
{
        g_print("Current: %"GST_TIME_FORMAT"   Total: %" GST_TIME_FORMAT "r", 
            GST_TIME_ARGS(m_position),GST_TIME_ARGS(m_length));
}

5、GStreamer元件狀態

一個元件在被建立後,它不會執行任何操作,通過改變元件的狀態,才能使它做某些事情。元件有四種狀態,每種狀態都有其特定的意義,具體如下:

GST_STATE_NULL 預設狀態:沒有分配任何資源,沒有載入外掛,不能處理資料;

GST_STATE_READY 預備狀態:分配或載入所有與流無關的資源(非硬體資源),所有資料流的位置資訊應該自動置0,如果資料流先前被開啟過,它應該被關閉,並且其位置資訊、特性資訊應該被重新置為初始狀態;

GST_STATE_PAUSED 暫停狀態:準備好全部資源,接受資料流,只是sink element暫停,收到資料不處理,只是block;

GST_STATE_PLAYING 播放狀態:準備好全部資源,接受並處理資料流;其實這個狀態除了當前執行時鐘外,其它與PAUSED狀態一樣,可以通過gst_element_set_state()來改變一個元件的狀態,當元件處於GST_STATE_PLAYING狀態,管道會開始自動處理資料。

6、GStreamer中的幾個關鍵概念

6.1、識別流的MIME型別

元件通過caps來描述其能處理的媒體格式,元件之間互動資料流通過caps協商,caps是一個mime型別或者一些特性集的組合。

一個載入進系統的元件必須提供其源襯墊和接收襯墊支援的mime型別。通過Gstreamer註冊中心可以知道目前註冊的不同的元件,以及他們所期望得到的與他們能夠產生的媒體型別,下圖顯示了管道中每個Pads所處理的MIME型別。

6.2、媒體流型別檢測(typefind)

通常當載入一個新的媒體流時,媒體的型別並不明瞭。這意味著選擇一條管道來對媒體流進行解碼之前,首先需要檢測媒體流的型別。 GStreamer 使用了型別檢測 (typefinding) 來達到此目的。型別檢測是構建管道所必經的步驟。

首先它會一直讀取資料流,在此期間,它會把資料提供給所有的實現了型別檢測器 (typefinder) 的外掛,當其中任何一個型別檢測器識別出資料流,這個型別檢測器元件將會傳送一個訊號,並開始像一個關卡 (passthrough)模組一樣工作。如果資料流的型別沒有被任何型別檢測器識別出來,管道會傳送一個錯誤資訊,並終止所有正在處理該資料流的動作。一旦型別檢測元件找到一個型別,應用程式將會使用該元件作為管道的一部分來解碼媒體流。

6.3、資料探測

探測是襯墊監聽器的形象比喻,從技術上,探針僅僅是一個可以依附於襯墊的回撥訊號。這些訊號預設是沒有被髮射(fired)的(不然的話會降低效能),但是可以通過附加探針呼叫gst_pad_add_data_probe() 或類似的函數被啟用,這些函數附加了訊號處理器,並啟用實際訊號的發射。

同樣地,你可以用 gst_pad_remove_data_probe () 或相關函數來刪除訊號處理器,也可以只是監聽時間或緩衝區。 探針在管道的執行緒context執行,所以回撥不應該阻塞,而且通常不能有異常的阻塞,否則會降低管道的效能,如果出現這樣的缺陷,會導致死鎖甚至崩潰。

6.4、外掛載入流程

如下圖所示,基於外掛的程式,其工作原理本質上都是通過讀取動態庫實現的,只需要每個動態庫中實現某一個特定的介面就可以了,比如XX_init等,這裡就是plugin_init。裡面會有個像登入檔一樣的資料結構會儲存所有的外掛的資訊。

7、GStreamer開發範例-MP3檔案播放器

利用GStreamer框架提供的元件,來實現一個簡單的MP3播放器。資料來源元件負責從磁碟上讀取資料,過濾器元件負責對資料進行解碼,而接受器元件則負責將解碼後的資料寫入音效卡,範例程式碼和註釋如下:

#include <gst/gst.h>
#include <glib.h>
//定義訊息處理常式,
static gboolean bus_call(GstBus *bus,GstMessage *msg,gpointer data)
{
    GMainLoop *loop = (GMainLoop *) data;//主迴圈的指標,接受EOS訊息時退出
    switch (GST_MESSAGE_TYPE(msg))
    {
        case GST_MESSAGE_EOS:
            g_print("End of streamn");
            g_main_loop_quit(loop);
            break;
        case GST_MESSAGE_ERROR:
        {
            gchar *debug;
            GError *error;
            gst_message_parse_error(msg,&error,&debug);
            g_free(debug);
            g_printerr("ERROR:%sn",error->message);
            g_error_free(error);
            g_main_loop_quit(loop);
            break;
        }
        default:
             break;
    }
    return TRUE;
}
int main(int argc,char *argv[])
{
    GMainLoop *loop;
    GstElement *pipeline,*source,*decoder,*sink;//定義元件
    GstBus *bus;
    gst_init(&argc,&argv); //初始化gstreamer
    loop = g_main_loop_new(NULL,FALSE);//建立主迴圈,在執行 g_main_loop_run後正式開始迴圈
    if(argc != 2)
    {
        g_printerr("Usage:%s <mp3 filename>n",argv[0]);
        return -1;
    }
    //建立管道和元件
    pipeline = gst_pipeline_new("audio-player"); //管道用來容納元件
    source = gst_element_factory_make("filesrc","file-source");//資料來源元件
    decoder = gst_element_factory_make("mad","mad-decoder");//過濾器元件
    sink = gst_element_factory_make("autoaudiosink","audio-output");//接收器元件
    if(!pipeline||!source||!decoder||!sink){
        g_printerr("One element could not be created.Exiting.n");
        return -1;
    }
    //設定 source的location 引數,即檔案地址.
    g_object_set(G_OBJECT(source),"location",argv[1],NULL);
    //得到管道的訊息匯流排
    bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
    //新增訊息監視器
    gst_bus_add_watch(bus,bus_call,loop);
    gst_object_unref(bus);
    //把元件新增到管道中。管道是一個特殊的元件,可以更好的讓資料流動
    gst_bin_add_many(GST_BIN(pipeline),source,decoder,sink,NULL);
    //通過襯墊依次連線元件
    gst_element_link_many(source,decoder,sink,NULL);
    //啟動管道,開始播放
    gst_element_set_state(pipeline,GST_STATE_PLAYING);
    g_print("Runningn");
    //開始迴圈
    g_main_loop_run(loop);
    g_print("Returned,stopping playbackn");
    //終止管道,釋放資源
    gst_element_set_state(pipeline,GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(pipeline));
    return 0;
}

編譯執行

gcc-Wall$(pkg-config--cflags--libsgstreamer-0.10)-gtest2.c-otest2

./test2/home/phinecos/test.mp3

8、最後

本文總結了多媒體框架GStreamer一些基本概念及流程,希望能給使用GStreamer開源庫的朋友提供一個借鑑或參考。

到此這篇關於C語言多媒體框架GStreamer使用教學深講的文章就介紹到這了,更多相關C語言GStreamer內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com