用 GStreamer 簡化 Linux 多媒體開發

來源:互聯網
上載者:User

一、基本概念

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類型:

 
  1. GstCaps *caps; 
  2. caps = gst_pad_get_caps (pad); 
  3. g_print ("pad name is: %s/n", gst_pad_get_name (pad)); 
  4. while (caps) { 
  5.   g_print (" Capability name is %s, MIME type is %s/n",
  6.   gst_caps_get_name (cap),  gst_caps_get_mime (cap)); 
  7.   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應用程式中能夠像使用元件一樣使用這類箱櫃。下面的代碼示範了如何為箱櫃添加一個精靈襯墊:

 
  1. GstElement *bin; 
  2. GstElement *element; 
  3. element = gst_element_factory_create ("mad", "decoder"); 
  4. bin = gst_bin_new ("bin_name"); 
  5. gst_bin_add (GST_BIN (bin), element); 
  6. gst_element_add_ghost_pad(bin,gst_element_get_pad(element,"sink"),"sink"); 

二、元件串連

在引入了元件和襯墊的概念之後,GStreamer對多媒體資料的處理過程就變得非常清晰了:通過將不同元件的襯墊依次串連起來構成一條ApsaraVideo for Media Processing管
道,使 資料在流經管道的過程能夠被各個元件正常處理,最終實現特定的多媒體功能。
圖1就描述了一條很簡單的管道,它由三個基本元件構成:資料來源元件只負責產生資料,它的輸出襯墊與過濾器元件的輸入襯墊相連;過濾器元件負責從自己的輸入

襯墊中擷取資料,並在經過特定的處理之後,將結果通過輸出襯墊傳給與之相連的接收器元件;接收器元件只負責接收資料,它的輸入襯墊與過濾器元件的輸出襯墊
相連,負責對最終結果進行相應的處理。
GStreamer架構中的元件是通過各自的襯墊串連起來的,下面的代碼示範了如何將兩個元件通過襯墊串連起來,以及如何在需要的時候斷開它們之間的連
接:

 
  1. GstPad *srcpad, *sinkpad; 
  2. srcpad = gst_element_get_pad (element1, "src"); 
  3. sinpad = gst_element_get_pad (element2, "sink"); 
  4. // 串連 
  5. gst_pad_link (srcpad, sinkpad); 
  6. // 斷開 
  7. 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播放器的原始碼如下所示:

 
  1. #include <gst/gst.h> 
  2.  
  3. int main (int argc, char *argv[]){ 
  4.     GstElement *pipeline, *filesrc, *decoder, *audiosink; 
  5.     gst_init(&argc, &argv); 
  6.     if (argc != 2) { 
  7.         g_print ("usage: %s <mp3 filename>/n", argv[0]); 
  8.         exit (-1); 
  9.     } 
  10.     /* 建立一條新的管道 */ 
  11.     pipeline = gst_pipeline_new ("pipeline"); 
  12.     /* 產生用於讀取硬碟資料的元件 */ 
  13.     filesrc = gst_element_factory_make ("filesrc", "disk_source"); 
  14.     g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL); 
  15.     /* 建立解碼器元件 */ 
  16.     decoder = gst_element_factory_make ("mad", "decoder"); 
  17.     /* 建立音頻回放元件 */ 
  18.     audiosink = gst_element_factory_make ("osssink", "play_audio"); 
  19.     /* 將產生的元件添加到管道中 */ 
  20.     gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, audiosink, NULL); 
  21.     /* 串連各個元件 */ 
  22.     gst_element_link_many (filesrc, decoder, audiosink, NULL); 
  23.     /* 開始播放 */ 
  24.     gst_element_set_state (pipeline, GST_STATE_PLAYING); 
  25.     while (gst_bin_iterate (GST_BIN (pipeline))); 
  26.     /* 停止管道處理流程 */ 
  27.     gst_element_set_state (pipeline, GST_STATE_NULL); 
  28.     /* 釋放佔用的資源 */ 
  29.     gst_object_unref (GST_OBJECT (pipeline)); 
  30.     exit (0); 

五、小結

隨著 GNOME 案頭環境的不斷普及,GStreamer 作為一個強大的多媒體應用開發架構,已經開始受到越來越多人的關注。Gstreamer在設計時採用了非常靈活的體繫結構,並且提供了許多預定義的媒體處 理模組,因此能夠極大簡化在Linux

下開發多媒體應用的難度。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.