一、基本概念
GStreamer 作為 GNOME 案頭環境推薦的流媒體
應
用架構,採用了基於外掛程式(plugin)和管道(pipeline)的體繫結構,架構中的所有的功能模組都被實現成可以插拔的組件
(component),並且在需要的時候能夠很方便地安裝到任意一個管道上,由於所有外掛程式都通過管道機制進行統一的資料交換,因此很容易利用已有的各種
外掛程式“組裝”出一個功能完 善的多媒體應用程式。
1.1 元件處理
對於需要應用 GStreamer 架構的程式員來講,GstElement
是一個必須理解的概念,因為它是組成管道的基本構件,也是架構中所有可用組件的基礎,這也難怪 GStreamer 架構中的大部分函數都會涉及到對
GstElement 對象的操作。從 GStreamer 自身的觀點來看,GstElement
可以描述為一個具有特定屬性的黑盒子,它通過連接點(link point)與外界進行互動,向架構中的其餘部分表徵自己的特性或者功能。
按照各自功能上的差異,GStreamer 又將 GstElement 細分成如下幾類: •Source Element
資料來源元件 只有輸出端,它僅能用來產生供管道消費的資料,而不能對資料做任何處理。一個典型的資料來源元件的例子是音頻捕獲單元,它負責從音效卡讀取原始的
音頻資料,然後作為資料來源提供給其它模組使用。 •Filter Element
過濾器元件 既有輸入端又有輸出端,它從輸入端獲得相應的資料,並在經過特殊處理之後傳遞給輸出端。一個典型的過濾器元件的例子是音頻編碼
單 元,它首先從外界獲得音頻資料,然後根據特定的壓縮演算法對其進行編碼,最後再將編碼後的結果提供給其它模組使用。 •Sink Element
接收器元件 只有輸入端,它僅具有消費資料的能力,是整條媒體管道的終端。一個典型的接收器元件的例子是音頻回放單元,它負責將接收到的資料寫到音效卡上,
通常這也是音頻處理過程中的最後一個環節。
圖1將有助於你更好地理解資料來源元件、過濾器元件和接收器元件三者的區別,同時也不難看出它們是如何相互配合形成管道的:
圖 1
需要注意的是,過濾器元件的具體形式是非常靈活的,GStreamer並沒有嚴格規定輸入端和輸出端的數目,事實上它們都可以是一個或者多個。圖2
是一
個AVI分離器的基本結構,它能夠將輸入資料分離成單獨的音頻資訊和視頻資訊,用於實現該功能的過濾器元件很明顯只具有一個輸入端,但卻需要有兩個輸出
端。
圖 2
要想在應用程式中建立GstElement對象,唯一的辦法是藉助於工廠對象GstElementFactory。由於GStreamer架構提供
了多
種類型的GstElement對象,因此對應地提供了多種類型的GstElementFactory對象,它們是通過特定的工廠名稱來進行區分的。例如,
下面的代碼通過gst_element_factory_find()函數獲得了一個名為mad的工廠對象,它之後可以用來建立與之對應的MP3解碼器元
件:
GstElementFactory *factory; factory = gst_element_factory_find ("mad");
成功獲得工廠對象之後,接下來就可以通過gst_element_factory_create()函數來建立特定的GstElement對象了,該函數
在調用時有兩個參數,分別是需要用到的工廠對象,以及即將建立的元件名稱。元件名稱可以用查詢的辦法獲得,也可以通過傳入null 指標(NULL)來產生工廠對
象的預設元件。下面的代碼示範了如何利用已經獲得的工廠對象,來建立名為decoder的MP3解碼器元件:
GstElement *element;element = gst_element_factory_create (factory, "decoder");
當建立的GstElement不再使用的時候,還必須調用gst_element_unref()函數釋放其佔用的記憶體資源:
gst_element_unref (element);
GStreamer
使用了與GObject相同的機制來對屬性(property)進行管理,包括查詢(query)、設定(set)和讀取(get)等。所有的
GstElement對象都需要從其父物件GstObject那裡繼承名稱(name)這一最基本的屬性,這是因為像
gst_element_factory_make()和gst_element_factory_create()這樣的函數在建立工廠對象和元件對象
時都會用到名稱屬性,通過調用gst_object_set_name()和gst_object_get_name()函數可以設定和讀取
GstElement對象的名稱屬性。
1.2 襯墊處理
襯墊(pad)是GStreamer架構引入的另外一個基本概念,它指的是元件(element)與外界的串連通道,對於架構中的某個特定元件來
說,其能
夠處理的媒體類型正是通過襯墊暴露給其它元件的。成功建立GstElement對象之後,可以通過gst_element_get_pad()獲得該元件
的指定襯墊。例如,下面的代碼將返回element元件中名為src的襯墊:
GstPad *srcpad;srcpad = gst_element_get_pad (element, "src");
如果需要的話也可以通過gst_element_get_pad_list()函數,來查詢指定元件中的所有襯墊。例如,下面的代碼將輸出element元件中所有襯墊的名稱:
GList *pads;pads =
gst_element_get_pad_list (element);while (pads) { GstPad *pad = GST_PAD
(pads->data); g_print ("pad name is: %s/n", gst_pad_get_name
(pad)); pads = g_list_next (pads);}
與元件一樣,襯墊的名稱也能夠動態設定或者讀取,這是通過調用gst_pad_get_name
()和gst_pad_set_name()函數來完成的。所有元件的襯墊都可以細分成輸入襯墊和輸出襯墊兩種,其中輸入襯墊只能接收資料但不能產生數
據,而輸出襯墊則正好相反,只能產生資料但不能接收資料,利用函數gst_pad_get_direction()可以獲得指定襯墊的類型。
GStreamer架構中的所有襯墊都必然依附於某個元件之上,調用gst_pad_get_parent()可以獲得指定襯墊所屬的元件,該函數的返回
值是一個指向GstElement的指標。
襯墊從某種程度上可以看成是元件的代言人,因為它要負責向外界描述該元件所具有的能力。GStreamer架構提供了統一的機制來讓襯墊描述元件所具有的
能力(capability),這是藉助資料結構_GstCaps來實現的:
struct _GstCaps { gchar *name;
/* the name of this caps */ guint16 id; /* type id (major type)
*/ guint refcount; /* caps are refcounted */ GstProps *properties; /*
properties for this capability */ GstCaps *next; /* caps can be chained
together */};
以下是
對mad元件的能力描述,不難看出該元件中實際包含sink和src兩個襯墊,並且每個襯墊都帶有特定的功能資訊。名為sink的襯墊是mad元件的輸入
端,它能夠接受MIME類型為audio/mp3的媒體資料,此外還具有layer、bitrate和framed三種屬性。名為src的襯墊是mad元
件的輸出端,它負責產生MIME類型為audio/raw媒體資料,此外還具有format、depth、rate和channels等多種屬性。
Pads:
SINK template: ’sink’
Availability: Always
Capabilities: ’mad_sink’:
MIME type: ’audio/mp3’:
SRC template: ’src’
Availability: Always
Capabilities: ’mad_src’:
MIME type: ’audio/raw’:
format: String: int
endianness: Integer: 1234
width: Integer: 16
depth: Integer: 16
channels: Integer range: 1 - 2
law: Integer: 0
signed: Boolean: TRUE
rate: Integer range: 11025 - 48000
準確地說,GStreamer架構中的每個襯墊都可能對應於多個能力描述,它們能夠通過函數gst_pad_get_caps()來獲得。例如,下面的代碼將輸出pad襯墊中所有能力描述的名稱及其MIME類型:
- GstCaps *caps;
- caps = gst_pad_get_caps (pad);
- g_print ("pad name is: %s/n", gst_pad_get_name (pad));
- while (caps) {
- g_print (" Capability name is %s, MIME type is %s/n",
- gst_caps_get_name (cap), gst_caps_get_mime (cap));
- caps = caps->next;
- }
1.3 箱櫃
箱櫃(bin)是GStreamer架構中的容器元件,它通常被用來容納其它的元件對象,但由於其自身也是一個GstElement對象,因此實際
上也能
夠被用來容納其它的箱櫃對象。利用箱櫃可以將需要處理的多個元件組合成一個邏輯元件,由於不再需要對箱櫃中的元件逐個進行操作,因此能夠很容易地利用它來
構造更加複雜的管道。在GStreamer架構中使用箱櫃還有另外一個優點,那就是它會試著對資料流進行最佳化,這對於多媒體應用來講是很具吸引力的。
圖3描述了箱櫃在GStreamer架構中的典型結構:
圖 3
在GStreamer應用程式中使用的箱櫃主要有兩種類型:
•GstPipeline 管道是最常用到的容器,對於一個GStreamer應用程式來講,其頂層箱櫃必須是一條管道。
•GstThread 線程的作用在於能夠提供同步處理能力,如果GStreamer應用程式需要進行嚴格的音視頻同步,一般都需要用到這種類型的箱櫃。
GStreamer架構提供了兩種方法來建立箱櫃:一種是藉助Factory 方法,另一種則是使用特定的函數。下面的代碼示範了如何使用Factory 方法建立線程對象,以及 如何使用特定函數來建立管道對象:
GstElement *thread, *pipeline;
// 建立線程對象,同時為其指定唯一的名稱。
thread = gst_element_factory_make ("thread", NULL);
// 根據給出的名稱,建立一個特定的管道對象。
pipeline = gst_pipeline_new ("pipeline_name");
箱櫃成功建立之後,就可以調用gst_bin_add()函數將已經存在的元件添加到其中來了:
GstElement *element;GstElement
*bin;bin = gst_bin_new ("bin_name");element = gst_element_factory_make
("mpg123", "decoder");gst_bin_add (GST_BIN (bin), element);
而要從箱櫃中找到特定的元件也很容易,可以藉助gst_bin_get_by_name()函數實現:
GstElement *element;element = gst_bin_get_by_name (GST_BIN (bin), "decoder");
由
於GStreamer架構中的一個箱櫃能夠添加到另一個箱櫃之中,因此有可能會出現箱櫃嵌套的情況,gst_bin_get_by_name()函數在查
找元件時會對嵌套的箱櫃作遞迴尋找。元件有添加到箱櫃之中以後,在需要的時候還可以從中移出,這是通過調用gst_bin_remove()函數來完成
的:
GstElement *element;gst_bin_remove (GST_BIN (bin), element);
如果仔細研究一3中描述的箱櫃,會發現它沒有屬於自己的輸入襯墊和輸出襯墊,因此顯然是無法作為一個邏輯整體與其它元件互動的。為瞭解決這一問
題,GStreamer引入了精靈襯墊(ghost
pad)的概念,它是從箱櫃裡面所有元件的襯墊中推舉出來的,通常來講會同時選出輸入襯墊和輸出襯墊,4所示:
圖4
具有精靈襯墊的箱櫃在行為上與元件是完全相同的,所有元件具有的屬性它都具有,所有針對元件能夠進行的操作也同樣能夠針對箱櫃進行,因此在GStreamer應用程式中能夠像使用元件一樣使用這類箱櫃。下面的代碼示範了如何為箱櫃添加一個精靈襯墊:
- GstElement *bin;
- GstElement *element;
- element = gst_element_factory_create ("mad", "decoder");
- bin = gst_bin_new ("bin_name");
- gst_bin_add (GST_BIN (bin), element);
- gst_element_add_ghost_pad(bin,gst_element_get_pad(element,"sink"),"sink");
二、元件串連
在引入了元件和襯墊的概念之後,GStreamer對多媒體資料的處理過程就變得非常清晰了:通過將不同元件的襯墊依次串連起來構成一條ApsaraVideo for Media Processing管
道,使 資料在流經管道的過程能夠被各個元件正常處理,最終實現特定的多媒體功能。
圖1就描述了一條很簡單的管道,它由三個基本元件構成:資料來源元件只負責產生資料,它的輸出襯墊與過濾器元件的輸入襯墊相連;過濾器元件負責從自己的輸入
襯墊中擷取資料,並在經過特定的處理之後,將結果通過輸出襯墊傳給與之相連的接收器元件;接收器元件只負責接收資料,它的輸入襯墊與過濾器元件的輸出襯墊
相連,負責對最終結果進行相應的處理。
GStreamer架構中的元件是通過各自的襯墊串連起來的,下面的代碼示範了如何將兩個元件通過襯墊串連起來,以及如何在需要的時候斷開它們之間的連
接:
- GstPad *srcpad, *sinkpad;
- srcpad = gst_element_get_pad (element1, "src");
- sinpad = gst_element_get_pad (element2, "sink");
- // 串連
- gst_pad_link (srcpad, sinkpad);
- // 斷開
- gst_pad_unlink (srcpad, sinkpad);
如果需要建立起串連的元件都只有一個輸入襯墊和一個輸出襯墊,那麼更簡單的做法是調用gst_element_link()函數直接在它們之間建立起串連,或者調用gst_element_unlink()函數斷開它們之間的串連:
// 串連
gst_element_link (element1, element2);
// 斷開
gst_element_unlink (element1, element2);
三、元件狀態
當GStreamer架構中的元件通過管道串連好之後,它們就開始了各自的處理流程,期間一般會經曆多次狀態切換,其中每個元件在特定時刻將處於如
下四種 狀態之一: •NULL 這是所有元件的預設狀態,表明它剛剛建立,還沒有開始做任何事情。 •READY
表明元件已經做好準備,隨時可以開始處理流程。 •PAUSED 表明元件因某種原因暫時停止處理資料。 •PLAYING 表明元件進行中資料處理。
所有的元件都從NULL狀態開始,依次經曆NULL、READY、PAUSED、PLAYING等狀態間的轉換。元件當前所處的狀態可以通過調用
gst_element_set_state()函數進行切換:
GstElement *bin;/* 建立元件,並將其串連成箱櫃bin */gst_element_set_state (bin, GST_STATE_PLAYING);
默
認情況下,管道及其包含的所有元件在建立之後將處於NULL狀態,此時它們不會進行任何操作。當管道使用完畢之後,不要忘記重新將管道的狀態切換回
NULL狀態,讓其中包含的所有元件能夠有機會釋放它們正在佔用的資源。
管道真正的處理流程是從第一次將其切換到READY狀態時開始的,此時管道及其包含的所有元件將做好相應的初始化工作,來為即將執行的資料處理過程做好准
備。對於一個典型的元件來講,處於READY狀態時需要執行的操作包括開啟媒體檔案和音訊裝置等,或者試圖與位於遠端的媒體伺服器
建
立起串連。
處於READY狀態的管道一旦切換到PLAYING狀態,需要處理的多媒體資料就開始在整個管道中流動,並依次被管道中包含的各個元件進行處理,從而最終
實現管道預先定義好的某種多媒體功能。GStreamer架構也允許將管道直接從NULL狀態切換到PLAYING狀態,而不必經過中間的READY狀
態。
正處於播放狀態的管道能夠隨時切換到PAUSED狀態,暫時停止管道中所有資料的流動,並能夠在需要的時候再次切換回PLAYING狀態。如果需要插入或
者更改管道中的某個元件,必須先將其切換到PAUSED或者NULL狀態,元件在處於PAUSED狀態時並不會釋放其佔用的資源。
四、實現MP3播放器
在理解了一些基本概念和處理流程之後,下面來看看如何利用GStreamer架構提供的組件,來實現一個簡單的MP3播放器。在圖1中描述的結構能
夠很容 易地映射成MP3播放器,其中資料來源元件負責從磁碟上讀取資料,過濾器元件負責對資料進行解碼,而接受器元件則負責將解碼後的資料寫入音效卡。
與其它眾多GNOME項目一樣,GStreamer也是用C語言
實現的。如果想要在程式中應用GStreamer提供的各種功能,首先必須在主函數中調用 gst_init()來完成相應的初始化工作,以便將使用者從命令列輸入的參數傳遞給GStreamer函數庫。一個典型的GStreamer應用程式的初 始化如下所示:
#include <gst/gst.h>
int main (int argc, char *argv[]){
gst_init (&argc, &argv);
/* ... */
}
接下去需要建立三個元件並串連成管道,由於所有GStreamer元件都具有相同的基類GstElement,因此能夠採用如下方式進行定義:
GstElement *pipeline, *filesrc, *decoder, *audiosink;
管道在GStreamer架構中是用來容納和管理元件的,下面的代碼將建立一條名為pipeline的新管道:
/* 建立用來容納元件的新管道 */ pipeline = gst_pipeline_new ("pipeline");
資料來源元件負責從磁碟檔案中讀取資料,它具有名為location的屬性,用來指明檔案在磁碟上的位置。使用標準的GObject屬性機制可以為元件設定相應的屬性:
/* 建立資料來源元件 */filesrc =
gst_element_factory_make ("filesrc", "disk_source");g_object_set
(G_OBJECT (filesrc), "location", argv[1], NULL);
過濾器元件負責完成對MP3格式的資料進行解碼,最簡單的辦法是安裝mad這一外掛程式,藉助它來完成相應的解碼工作:
/* 建立過濾器元件 */decoder = gst_element_factory_make ("mad", "decoder");
接收器元件負責將解碼後的資料利用音效卡播放出來:
/* 建立接收器元件 */
audiosink = gst_element_factory_make ("audiosink", "play_audio");
已經建立好的三個元件需要全部添加到管道中,並按順序串連起來:
/* 添加元件到管道中 */
gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, audiosink, NULL);
/* 通過襯墊串連元件 */
gst_element_link_many (filesrc, decoder, audiosink, NULL);
所有準備工作都做好之後,就可以通過將管道的狀態切換到PLAYING狀態,來啟動整個管道的資料處理流程:
/* 啟動管道 */gst_element_set_state (pipeline, GST_STATE_PLAYING);
由於沒有用到線程,因此必須通過不斷調用gst_bin_iterate()函數的辦法,來判斷管道的處理過程會在何時結束:
while (gst_bin_iterate (GST_BIN (pipeline)));
只要管道內還會繼續有新的事件產生,gst_bin_iterate()函數就會一直返回TRUE,只有當整個處理過程都結束的時候,該函數才會返回FALSE,此時就該終止管道並釋放佔用的資源了:
/* 終止管道 */gst_element_set_state (pipeline, GST_STATE_NULL);/* 釋放資源 */gst_object_unref (GST_OBJECT (pipeline));
用GStreamer實現的MP3播放器的原始碼如下所示:
- #include <gst/gst.h>
-
- int main (int argc, char *argv[]){
- GstElement *pipeline, *filesrc, *decoder, *audiosink;
- gst_init(&argc, &argv);
- if (argc != 2) {
- g_print ("usage: %s <mp3 filename>/n", argv[0]);
- exit (-1);
- }
- /* 建立一條新的管道 */
- pipeline = gst_pipeline_new ("pipeline");
- /* 產生用於讀取硬碟資料的元件 */
- filesrc = gst_element_factory_make ("filesrc", "disk_source");
- g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);
- /* 建立解碼器元件 */
- decoder = gst_element_factory_make ("mad", "decoder");
- /* 建立音頻回放元件 */
- audiosink = gst_element_factory_make ("osssink", "play_audio");
- /* 將產生的元件添加到管道中 */
- gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, audiosink, NULL);
- /* 串連各個元件 */
- gst_element_link_many (filesrc, decoder, audiosink, NULL);
- /* 開始播放 */
- gst_element_set_state (pipeline, GST_STATE_PLAYING);
- while (gst_bin_iterate (GST_BIN (pipeline)));
- /* 停止管道處理流程 */
- gst_element_set_state (pipeline, GST_STATE_NULL);
- /* 釋放佔用的資源 */
- gst_object_unref (GST_OBJECT (pipeline));
- exit (0);
- }
五、小結
隨著 GNOME 案頭環境的不斷普及,GStreamer 作為一個強大的多媒體應用開發架構,已經開始受到越來越多人的關注。Gstreamer在設計時採用了非常靈活的體繫結構,並且提供了許多預定義的媒體處 理模組,因此能夠極大簡化在Linux
下開發多媒體應用的難度。