網站首頁 編程語言 正文
之前參與的萬能視頻播放器項目采用了多媒體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 stream\n");
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:%s\n",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("Running\n");
//開始循環
g_main_loop_run(loop);
g_print("Returned,stopping playback\n");
//終止管道,釋放資源
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開源庫的朋友提供一個借鑒或參考。
原文鏈接:https://blog.csdn.net/chenlycly/article/details/125832524
相關推薦
- 2022-04-20 Android實現左側滑動菜單_Android
- 2022-12-23 C++多線程之帶返回值的線程處理函數解讀_C 語言
- 2021-12-13 Gin?框架快速創建靜態文件下載Web服務_Golang
- 2022-05-31 Python中的?if?語句及使用方法_python
- 2022-04-21 IDEA - Spring Boot 項目 application.yml 文件不加載的問題
- 2022-07-18 Column count doesn’t match value count at row 1
- 2022-12-24 VS2019調試C語言程序(監視操作)的詳細步驟_C 語言
- 2022-05-22 Python數據結構之隊列詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支