媒體知識雜談

媒體雜談和GStreamer

MPEG-4是一套用於音頻視頻信息的壓縮編碼標準,由國際標準化組織ISO)和國際電工委員會IEC)下屬的“動態圖像專家組”(Moving Picture Experts Group,即MPEG)制定,第一版在1998年10月通過,第二版在1999年12月通過。MPEG-4格式的主要用途在於網上光盤、語音傳送(視頻電話),以及電視廣播

MPEG-4包含了MPEG-1MPEG-2的絕大部份功能及其他格式的長處,並加入及擴充對虛擬現實模型語言(VRML , Virtual Reality Modeling Language)的支持,面向對象的合成文件(包括音效,視頻及VRML對象),以及數字版權管理DRM)及其他交互功能。而MPEG-4比MPEG-2更先進的其中一個特點,就是不再使用宏區塊做圖像分析,而是以圖像上個體爲變化記錄,因此儘管圖像變化速度很快、碼率不足時,也不會出現方塊畫面。

由於MPEG-4是一個公開的平臺,各公司、機構均可以根據MPEG-4標準開發不同的制式,因此市場上出現了很多基於MPEG-4技術的視頻格式,例如WMV 9Quick TimeDivXXvid等。MPEG-4大部份功能都留待開發者決定採用是否。這意味着整個格式的功能不一定被某個程序所完全函括。因此,這個格式有所謂配置profile)及級別level),定義了MPEG-4應用於不同平臺時的功能集合。

MPEG-4分部[編輯]

MPEG-4由一系列的子標準組成,被稱爲部 (part)(有時也譯爲),包括以下的部分:

·        第一部分(ISO/IEC 14496-1):系統:描述視頻和音頻數據流的控制、同步以及混合方式(即混流 Multiplexing,簡寫爲MUX)。

·        第二部分(ISO/IEC 14496-2):視頻:定義了一個對各種視覺信息(包括自然視頻、靜止紋理、計算機合成圖形等等)的編解碼器。(例如XviD編碼就屬於MPEG-4 Part 2)

·        第三部分(ISO/IEC 14496-3):音頻:定義了一個對各種音頻信號進行編碼的編解碼器的集合。包括高級音頻編碼(Advanced Audio Coding,縮寫爲AAC)的若干變形和其他一些音頻/語音編碼工具。

·        第四部分(ISO/IEC 14496-4):一致性:定義了對本標準其他的部分進行一致性測試的程序。

·        第五部分(ISO/IEC 14496-5):參考軟件:提供了用於演示功能和說明本標準其他部分功能的軟件

·        第六部分(ISO/IEC 14496-6):多媒體傳輸集成框架DMIF for Delivery Multimedia Integration Framework)

·        第七部分(ISO/IEC 14496-7):優化的參考軟件:提供了對實現進行優化的例子(這裏的實現指的是第五部分)。

·        第八部分(ISO/IEC 14496-8):在IP網絡上傳輸:定義了在IP網絡上傳輸MPEG-4內容的方式。

·        第九部分(ISO/IEC 14496-9):參考硬件:提供了用於演示怎樣在硬件上實現本標準其他部分功能的硬件設計方案。

·        第十部分(ISO/IEC 14496-10):高級視頻編碼或稱高級視頻編碼(Advanced Video Coding,縮寫爲AVC):定義了一個視頻編解碼器(codec)。AVC和XviD都屬於MPEG-4編碼,但由於AVC屬於MPEG-4 Part 10,在技術特性上比屬於MPEG-4 Part2的XviD要先進。另外,它和ITU-T H.264標準是一致的,故又稱爲H.264

·        第十二部分(ISO/IEC 14496-12):基於ISO的媒體文件格式:定義了一個存儲媒體內容的文件格式。

·        第十三部分(ISO/IEC 14496-13):知識產權管理和保護(IPMP for Intellectual Property Managementand Protection)拓展。

·        第十四部分(ISO/IEC 14496-14):MPEG-4文件格式:定義了基於第十二部分的用於存儲MPEG-4內容的視頻文件格式

·        第十五部分(ISO/IEC 14496-15):AVC文件格式:定義了基於第十二部分的用於存儲第十部分的視頻內容的文件格式。

·        第十六部分(ISO/IEC 14496-16):動畫框架擴展(AFX : Animation Framework eXtension)。

·        第十七部分(ISO/IEC 14496-17):同步文本字幕格式。

·        第十八部分(ISO/IEC 14496-18):字體壓縮和流式傳輸(針對開放字體格式 Open Font Format)。

·        第十九部分(ISO/IEC 14496-19):合成材質流(Synthesized Texture Stream)。

·        第二十部分(ISO/IEC 14496-20):簡單場景表示(LASeR for Lightweight Scene Representation。

·        第二十一部分(ISO/IEC 14496-21):用於描繪(Rendering)的MPEG-J拓展。

·        第二十二部分(ISO/IEC 14496-22):開放字體格式(Open Font Format)。

·        第二十三部分(ISO/IEC 14496-23):符號化音樂表示(Symbolic Music Representation)。

·        第二十四部分(ISO/IEC 14496-24):音頻與系統交互作用(Audio and systems interaction)。

·        第二十五部分(ISO/IEC 14496-25):3D圖形壓縮模型(3D Graphics Compression Model)。

·        第二十六部分(ISO/IEC 14496-26):音頻一致性檢查:定義了測試音頻數據與ISO/IEC 14496-3是否一致的方法(Audio conformance)。

·        第二十七部分(ISO/IEC 14496-27):3D圖形一致性檢查:定義了測試3D圖形數據與ISO/IEC 14496-11:2005, ISO/IEC14496-16:2006, ISO/IEC 14496-21:2006, 和 ISO/IEC 14496-25:2009是否一致的方法(3D Graphics conformance)。

Profiles是在每個部分內定義的,所以對某個部分的一個實現通常不是對該部分的完整實現。

MPEG-1MPEG-2MPEG-7MPEG-21是由MPEG制定的其他MPEG標準。

 

Gstreamer

Gstreamer是個好東西,張一元的花茶好喝,今天用五花肉煉的油我看行;-) 

Gstreamer java版本cortado不再繼續更新,被淘汰了?

GStreamer plug-ins:
1 protocols handling
2 sources: for audio and video (involves protocol plugins)
3 formats: parsers, formaters, muxers, demuxers, metadata, subtitles
4 codecs: coders and decoders
5 filters: converters, mixers, effects, 
6 sinks: for audio and video (involves protocol plugins)

Specifically, GStreamer provides
1 an API for multimedia applications
2 a plugin architecture
3 a pipeline architecture
4 a mechanism for media type handling/negociation
5 over 150 plug-ins
6 a set of tools
來自http://blog.sina.com.cn/xingqx 講解了gstreamer的部分內容。
GStreamer
GNOME 桌面環境下用來構建流媒體應用的編程框架(framework),其目標是要簡化音/視頻應用程序的開發,目前已經能夠被用來處理像 MP3OggMPEG1MPEG2AVIQuicktime 等多種格式的多媒體數據。

一、基本概念 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)來生成工廠對象的默認元件。下面的代碼示範瞭如何利用已經獲得的工廠對象,來創建名爲decoderMP3解碼器元件:

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; 
guint16 id; 
guint refcount; 
GstProps *properties; 
GstCaps *next; 
}; 

以下是對mad元件的能力描述,不難看出該元件中實際包含sinksrc兩個襯墊,並且每個襯墊都帶有特定的功能信息。名爲sink的襯墊是mad元件的輸入端,它能夠接受MIME類型爲audio/mp3的媒體數據,此外還具有layerbitrateframed三種屬性。名爲src的襯墊是 mad元件的輸出端,它負責產生MIME類型爲audio/raw媒體數據,此外還具有formatdepthratechannels等多種屬性。

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框架提供了兩種方法來創建箱櫃:一種是藉助工廠方法,另一種則是使用特定的函數。下面的代碼示範瞭如何使用工廠方法創建線程對象,以及如何使用特定函數來創建管道對象:

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對多媒體數據的處理過程就變得非常清晰了:通過將不同元件的襯墊依次連接起來構成一條媒體處理管道,使數據在流經管道的過程能夠被各個元件正常處理,最終實現特定的多媒體功能。

圖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狀態開始,依次經歷NULLREADYPAUSEDPLAYING等狀態間的轉換。元件當前所處的狀態可以通過調用gst_element_set_state()函數進行切換:

GstElement *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下開發多媒體應用的難度。

補充

除了上述Gstreamer中描述的組件外,還有busbufferevent等:
Bus:

       pipeline線程嚮應用程序發送發送消息,目的是對應用屏蔽線程的概念。缺省情況下,每個pipeline包含一個bus,所以應用不需要創建bus,僅僅需要在bus上設定一個對象信號處理類似的消息處理。當主循環開始運行後,bus週期性的檢查新消息,如果有消息,那麼調用註冊的回調函數。如果使用glib,那麼glib有對應的處理機制,否則使用信號機制。

流過pipeline的數據由buffersevents組成。buffers中是確切的pipeline數據,events中是控制信息,比如seek信息,end-of-stream指示等。src-element通常創建一個新的buffer,通過pad傳遞到鏈中的下一個element

buffer:

       buffer包含由(內存指針,內存大小,時間戳,引用計數等信息)。簡單的使用方式是:創建buffer,分配內存,放入數據,傳輸到後續element,後續element讀數據,處理後減去引用計數。更復雜的情況是element就地修改buffer中的內容,而不是分配一個新的bufferelement還寫到硬件內存(video-capture src等),bufer還可以是隻讀的。

event:

       Event控制包,可以發送給上/下游element。發送給下游的event統治後續element流狀態,是斷絕,涮新,流結尾等信息。發送給上游event用在應用交互和event-event交互,用來請求改變流狀態,例如seek等。對於應用來說,只有上流event比較重要。下流event僅僅維持一個完整的數據流概念。

 

 

Gstreamer說明

 Gstreamer簡介

是一個框架,靈活輕便。

第一部分基本沒有難度,只要能看懂英文。從我目前接觸的感覺上看,Gstreamer確實簡化了動態 庫的加載,模塊與模塊間的合作。

但是Gstreamer用得還是有點不太習慣,可能是 GLIB這種風格沒有適應。

gstreamer整個分爲:

l         core:核心庫

l         基礎插件:一些很基礎的插件

l         好插件:編寫質量較好的遵循LGPL協議的插件

l         壞插件:有待改進的插件

l         其他庫

1.1    核 心庫

核心庫是不瞭解任何媒體信息的,它只是一個框架,將所有單元聯繫起來。

單元是gstreamer裏的核心概念。

 基礎知識

2.1 單元

Element是構成管道的組件,每個element實際就是一個插件,在gst中得到組裝成一個pipe,數據從源單元流向目的單元,完成整個流程。單元間是可以鏈接起來的(必須得鏈接起來以組成pipe)。

2.2 Pad

pad是一個單元的輸入輸出端口,通過pad, 才能將兩個單元鏈接到一起。對輸入來說,pad就是一個插口,對輸出來說pad就是一個塞子。pad有自己的規格,所以不同規格的pad就限制了數據的規格。只有規格相符的pad才能鏈接到一起。

l         規格協商的過程叫caps negotiation

l         數據類型叫GstCaps

2.3 盒子和管道

盒子Bin是一組單元的集合,而管道Pipeline是一種特殊的盒子,在管道中,所有單元可以一體執行某些。

在實現的時候,Bin也是一種單元,操縱Bin就可以改變內部所有單元的屬性,而且Bin還能傳遞內部單元的信號事件。這樣就簡化了外界使用的難度。

管道是一個頂層的bin,可以設置狀態爲PAUSED或者PLAYING。內部會啓動一個獨立線程來幹活。

 GST構建

3.1 初始化

GST庫必須先初始化,調用gst_init

3.2 單元elements

GstElement是最重要的對象。一些高級對象也是從它派生出來的。有好幾種類型的elements,必須分清楚了。

1.       源單元

source單元是數據的產生方,對應一個源pad。一般畫在右邊。

l         源單元只能生產數據,不能接收數據

2.       中間單元

中間單元包括過濾器,轉換器,複用器,解複用器,編解碼器等。

它有多個源pad,對應多個目標pad

3.       目標單元

只能接受數據。

4.       創建和使用單元

通過factory_makegst_object_unref來創建及釋放單元。make需要兩個參數,一個是工廠名,一個是單元名。工廠名實際就是插件名,所以需要先加載插件上 來,才能創建對應的單元。

單元繼承所有Gobject的屬性,所以可以當做Gobject來處理。

單元有屬性,單元還能觸發信號,所以必須關注這些。

作爲工廠,其功能還不僅限於創建單元,一個工廠有屬性,它知道自己能創建怎樣的單元。

其實就是這個插件知道自己能創建怎樣的單元。可能需要看了插件編寫才真正知道。

5.       鏈接單元

單元必須鏈接起來,才能協同工作。

源單元-à中間單元--à目標單元

l         必須先加到管道後才能link起來。

l         不在同一bin中的不能link

6.       單元狀態

單元鏈接好後,啥事也不會發生,除非設置單元狀態。單元一共有四種狀態:

l         GST_STATE_NULL:默認 狀態,內部會釋放單元的所有資源,其實就是初始狀態。

l         XXX_READY:就緒狀態,分配 資源,打開設備。但是流不會打開,所以此時流信息都是零。如果之前打開了流,在這狀態中將會被關閉,流信息都會被重設。

l         XXX_PAUSED:已經打開了 流,但是暫時不處理它。這個時候可以去修改例如seek位置等流信息。時間軸停止

l         XXX_PLAYING:時間軸運 行。設置爲這個狀態後,整個流程就開始啓動了。內部會將消息發送從管道所在的線程轉移到應用程序線程。(?)

通過set_state函數設置狀態,GST會平滑設置,例如從PLAY設置爲NULL,將平滑設置READYPAUSED

3.4 Bins

Bin不僅是一個單元的集合,更是集成了對內部單元的管理。管道是一種特殊的bin,實際要播放一個視音頻就得用到管道。管道能獨立在後臺運行。

1.       創建Bin

Binelement派生而來,所以創建 單元的函數均可創建Bin,但一般用兩個更方便的函數:

l         gst_bin_new

l         gst_pipeline_new

Bin是一個集合,需要將單元加入進去,也可以刪除。

l         gst_bin_add,gst_bin_remove

注意,一旦加入到Bin,單元的所有者就變爲Bin了,所以刪除Bin的話,內部的單元也會相應減少引用。

2.       定製化Bin

還是得看插件編程指南才能真正理解。

3.5 Bus

1. 總論

Bus的好處是可以把pipeline所在的線程的消息 路由到應用程序指定的那個Context中去。

是全局那個線程嗎?待會查看下源碼。定期檢查bus?設計還是比較巧妙的。

bus上要加入監控回調函數。通過

l         gst_bus_add_watch/gst_bus_add_signal_watch

l         要從bus中取消息,得調用gst_bus_pop/peek/pop

注意,Bus發出的消息是GstMessage結構,值得解釋。

Bus含一個隊列,每次post一 個消息就加到隊列裏,然後出發maincontext的wakeup。 這樣就完成了將消息路由到maincontext去了。因爲maincontext等 待的有這個bus隊列。

這裏邊繞啊繞的..想法還是很直觀。

有沒有辦法不用默認context呢?有一個函數將message轉換爲signalemit

l         gst_bus_async_signal_func

這裏要區分下messagesignalsignalGobject系統提供的,messageGST提供的。message的處理是異步的,而signal的處理是同步的。

如果你不想寫很多switch來區別message的話,那麼另外一種辦法就是註冊對應的signal到系統。另外,如果不使 用mainloop的話,異步消息-信號不會發出去。

bus的消息分爲異步和同步兩類。

l         異步信號是通過加入到mainloop中的Gsource來觸發的,所以必須有mainloop再運行纔可

l         同步信號必須先註冊一個同步信號處理函數纔可。

 

需要自己設置一個同步信號處理函數,在那裏觸發另外一個context,並且調用上面這個函 數發送signal

 

2.  message類型

描述一個message有以下:

l         Message的來源:記錄是從哪個 單元發出來的

l         類型:

l         時間戳

比較重要的是類型信息:有以下幾類類型

l         error,warning,info等 信息:需要調用對應API進行解析

l         EOS:文件結尾,需要重設管道狀態 等

l         Tag:標籤信息(其實就是媒體信 息,比如長度,採樣率等)

l         State變化:

l         緩存信息:調用gst_bus_get_structure來解析緩存信息

l         單元信息:某些單元會發送特殊的信息

l         應用程序自己的信息

 

3.6 Pads和屬性

Pads很關鍵,代表了單元的出入口。標示Pads特性的有兩個:

l         方向,從單元內角度看,sink收數據,source發數據

l         可用性(availability

方向好說,只有sinksource兩種,可用性是一個新概念。主要代表這個pad是存活期的。

分三種:

l         pad一直存在

l         動態存在:有時候有,有時候沒有,動態創建和刪除

l         響應存在:根據外界要求來創建

這個可用性是針對媒體文件類型的一種簡化表示。下面針對這個具體講述,pad的可用性非常重要。

1. 動態pad

爲何會有動態之說?原因很簡單。例如播放音頻的時候,要動態檢測有幾路音頻,然後再創建對應的pad

程序裏邊應該綁定一個消息處理函數到動態pad的創建通知上。

2. 響應pad

響應用戶要求而創建的pad。必須從一個支持創建這種類型的單元中去創建,調用

l         gst_element_get_request_pad

這個element必須支持Request這種,這個熟悉由插件註冊的時候指定的。

還有一種就是查找相容的pad

l         get_element_get_compatible_pad,根據源padcaps來從單元中找一個相容的。

 

3. Pad的屬性

剛纔提到過,查找相容的pad,那麼相容是怎麼判斷和體現的呢?pad有自己的能力熟悉(Capabilities

pad的能力熟悉是和pad模板以及pad綁定一起的。pad模板估計就是一個pad工廠。

一個pad有很多不同的能力,這個是最原始的信息。但是具體工作後,一個pad要和別的pad協商,大家按照規定的能力辦 事。這樣,pad的能力就是協商後的能力了。

能力在GST中用GstCaps來表示。

GstCaps含一到多個Gstructure,一個Gstructure代表一種pad能處理的媒體類型。

4. GST中屬性和值的表示

GST除了使用GLIB中的數據類型外,還單獨定 義了一些數據類型,用來表示屬性值。

值得注意的有:

l         GST_TYPE_INT_RANGE:範圍值

l         GST_TYPE_LIST:包含任 何基本類型都可以

l         GST_TYPE_ARRAY:只能 包含相同的類型

5. Caps的用處

Caps實際的用處很多,其實就是一個尋找匹配padelement之用。

Caps中有一項描述媒體信息的,叫metadata。如何從caps中獲取條目呢?

caps中存的是structure條 目,一個structure代表一種能力

gst_caps_get_structure/size

根據條目多少和屬性,caps可分爲:

l         簡單caps:只含一條structure

l         固定caps:含一條structure,並且屬性值沒有range之類可變化的

l         任意caps和空caps是兩種特殊cap

 

6. 創建過濾器使用的caps

剛纔講的全是從單元中獲取caps,都是已經弄好了的。那麼如果想動態創建caps該如何做呢?

l         gst_caps_new_simple:創建simple

l         xx_full:創建nstructurecaps

這還只是創建pad,要把srcdst通過過濾單元鏈接起來,用

gst_element_link_filtered,內部會根據過濾pad自動創建一個capsfilter

所以關閉鏈接的時候,需要把src和dst分別從capsfilter中關閉鏈接。而非簡單 的關閉源和目標

 

7. 幽靈pad

有啥用?其實就是創建一個代理pad吧。

爲啥要有個這個東西?因爲bin本身是沒有pad的。所以你就沒辦法把兩個bin鏈接起來。

這個時候,可以用bin中的一個單元的pad構造一個代理pad,這樣bin就有一個代理pad了。這個pad實際指向被代理的那個單元的pad

 

3.7 緩衝與事件

數據流動是以緩衝傳遞來實際工作的,所以buffer比較在重要。

eventsmessage不太一樣,這個events實際就是命令,而且在 管道中流動。這麼說的話,buffer對應的就是數據。

從命名習慣上來說,buffer更應該看成是一種容器,裏邊含data和events。

1. Buffer

GstBuffer有以下成員:

l         數據內存指針

l         緩衝大小

l         時間戳

l         用者計數(與引用計數對應)

l         標識

2. Events

事件是一種控制數據,能夠在管道中上下流動。

一般來說,上游的控制命令可能是真的在控制什麼,來自下游的events可 能大多數是些狀態通知之類的。?原文是這麼說的。

應用程序自己能發送控制?例如seek命令。

恩,確實應該有地方可以發送控制命令。典型的就是seek。用戶也需要一個地方能做這個工作。

看來都是通過events方式來做到控制的。

l         gst_events_new_xxx

l         gst_element_send_event

先創建一個命令,然後發出去….

 

 

 

   餓漢式與懶漢式的區別:

 

  餓漢式:
        public class Singleton{
            private static Singleton singleton = new Singleton ();
            private Singleton (){}
            public Singleton getInstance(){return singletion;}
       } 

     
懶漢式:
       public class Singleton{
            private static Singleton singleton = null;
            public static synchronized synchronized getInstance(){
                 if(singleton==null){
                     singleton = new Singleton();
                 }
                return singleton;
            }
       } 

     
比較:
         
餓漢式是線程安全的,在類創建的同時就已經創建好一個靜態的對象供系統使用,以後不在改變
          
懶漢式如果在創建實例對象時不加上synchronized則會導致對對象的訪問不是線程安全的
          
推薦使用第一種 

 

1樓基本上已經回答了問題.但是懶漢式沒有加私有的構造函數
從實現方式來講他們最大的區別就是懶漢式是延時加載,
他是在需要的時候才創建對象,而餓漢式在虛擬機啓動的時候就會創建,
使用的場合根據具體環境和個人習慣吧.

 

 

 

談談碼率,幀率,分辨率和清晰度的關係 

爲了瞭解視頻的碼率、幀率、分辨率。我們先來看看視頻編碼的基本原理:視頻圖像數據有極強的相關性,也就是說有大量的冗餘信息。其中冗餘信息可分爲空域冗餘信息和時域冗餘信息。壓縮技術就是將數據中的冗餘信息去掉(去除數據之間的相關性),壓縮技術包含幀內圖像數據壓縮技術、幀間圖像數據壓縮技術和熵編碼壓縮技術。視頻文件一般涉及到三個參數:幀率、分辨率和碼率。

   
幀率:每秒顯示的圖片數。影響畫面流暢度,與畫面流暢度成正比:幀率越大,畫面越流暢;幀率越小,畫面越有跳動感。由於人類眼睛的特殊生理結構,如果所看畫面之幀率高於16的時候,就會認爲是連貫的,此現象稱之爲視覺暫留。並且當幀速達到一定數值後,再增長的話,人眼也不容易察覺到有明顯的流暢度提升了。  

分辨率:(矩形)圖片的長度和寬度,即圖片的尺寸  

碼率:把每秒顯示的圖片進行壓縮後的數據量。影響體積,與體積成正比:碼率越大,體積越大;碼率越小,體積越小。(體積=碼率×時間)  幀率X分辨率=壓縮前的每秒數據量(單位應該是若干個字節)   壓縮比=壓縮前的每秒數據量/碼率(對於同一個視頻源並採用同一種視頻編碼算法,則:壓縮比越高,畫面質量越差。) 

 所謂清晰,是指畫面十分細膩,沒有馬賽克。並不是分辨率越高圖像就越清晰。  簡單說:在碼率一定的情況下,分辨率與清晰度成反比關係:分辨率越高,圖像越不清晰,分辨率越低,圖像越清晰。在分辨率一定的情況下,碼率與清晰度成正比關係,碼率越高,圖像越清晰;碼率越低,圖像越不清晰。  

但是,事實情況卻不是這麼簡單。可以這麼說:在碼率一定的情況下,分辨率在一定範圍內取值都將是清晰的;同樣地,在分辨率一定的情況下,碼率在一定範圍內取值都將是清晰的。  在視頻壓縮的過程中, I幀是幀內圖像數據壓縮,是獨立幀。而P幀則是參考I幀進行幀間圖像數據壓縮,不是獨立幀。在壓縮後的視頻中絕大多數都是P幀,故視頻質量主要由P幀表現出來。由於P幀不是獨立幀,而只是保存了與鄰近的I幀的差值,故實際上並不存在分辨率的概念,應該看成一個二進制差值序列。而該二進制序列在使用熵編碼壓縮技術時會使用量化參數進行有損壓縮,視頻的質量直接由量化參數決定,而量化參數會直接影響到壓縮比和碼率。  視頻質量可以通過主觀和客觀方式來表現,主觀方式就是通常人們提到的視頻清晰度,而客觀參數則是量化參數或者壓縮比或者碼率。在視頻源一樣,壓縮算法也一樣的前提下比較,量化參數,壓縮比和碼率之間是有直接的比例關係的。  分辨率的變化又稱爲重新採樣。由高分辨率變成低分辨率稱爲下采樣,由於採樣前數據充足,只需要儘量保留更多的信息量,一般可以獲得相對較好的結果。而由低分辨率變成高分辨率稱爲上採樣,由於需要插值等方法來補充(猜測)缺少的像素點,故必然會帶有失真,這就是一種視頻質量(清晰度)的損失。 

 

 

 

視頻碼率,幀率和分辨率到底哪一個影響電影的清晰度

 

碼率:影響體積,與體積成正比:碼率越大,體積越大;碼率越小,體積越小。

碼率就是數據傳輸時單位時間傳送的數據位數,一般我們用的單位是kbps即千位每秒。也就是取樣率(並不等同與採樣率,採樣率的單位是Hz,表示每秒採樣的次數),單位時間內取樣率越大,精度就越高,處理出來的文件就越接近原始文件,但是文件體積與取樣率是成正比的,所以幾乎所有的編碼格式重視的都是如何用最低的碼率達到最少的失真,圍繞這個核心衍生出來cbr(固定碼率)與vbr(可變碼率), “碼率”就是失真度,碼率越高越清晰,反之則畫面粗糙而多馬賽克。

 

下面是通過一個wav文件的採樣率來計算碼率和文件大小,通過MediaInfo工具顯示的文件信息如下:

概要

完整名稱                            :audio\wav\adele-rolling_in_the_deep.wav

文件格式                            : Wave

文件大小                            : 38.3 MiB

長度                                   : 3分 47秒

平均混合碼率                    : 1 411 Kbps

 

音頻

ID                                        : 0

文件格式                            : PCM

格式設置,Endianness     : Little

編碼設置ID                         : 1

編碼設置ID/提示信息        : Microsoft

長度                                     : 3分 47秒

碼率                                     : 1 411.2 Kbps

聲道                                    : 2聲道

採樣率                                : 44.1 KHz

位深度                                : 16位

大小                                    : 38.3 MiB (100%)

 

1.碼率計算公式:

碼率=採樣率x 位深度 x 聲道

所以,上面文件的碼率= 44.1Khzx 16位x 2聲道 = 1411.2Kbps

 

2.文件大小= 碼率 x 時長 =1411.2 Kbps x (3 x 60 + 47 )s = 1411.2Kbps x 227s

 =38102.4Kb

38102.4Kb / 1024 Kb/M = 37.2M

近似等於mediainfo工具顯示的文件大小38.3M。

注:此計算公式對未壓縮的wav格式文件有效,不適用於mp3等被壓縮的文件。

 

幀率:影響畫面流暢度,與畫面流暢度成正比:幀率越大,畫面越流暢;幀率越小,畫面越有跳動感。如果碼率爲變量,則幀率也會影響體積,幀率越高,每秒鐘經過的畫面越多,需要的碼率也越高,體積也越大。

幀率就是在1秒鐘時間裏傳輸的圖片的幀數,也可以理解爲圖形處理器每秒鐘能夠刷新幾次,

 

分辨率:影響圖像大小,與圖像大小成正比:分辨率越高,圖像越大;分辨率越低,圖像越小。

 

清晰度

在碼率一定的情況下,分辨率與清晰度成反比關係:分辨率越高,圖像越不清晰,分辨率越低,圖像越清晰。
在分辨率一定的情況下,碼率與清晰度成正比關係,碼率越高,圖像越清晰;碼率越低,圖像越不清晰。

 

帶寬、幀率

例如在ADSL線路上傳輸圖像,上行帶寬只有512Kbps,但要傳輸4路CIF分辨率的圖像。按照常規,CIF分辨率建議碼率是512Kbps,那麼照此計算就只能傳一路,降低碼率勢必會影響圖像質量。那麼爲了確保圖像質量,就必須降低幀率,這樣一來,即便降低碼率也不會影響圖像質量,但在圖像的連貫性上會有影響。

 

avi幀率 dwScale,dwRate

轉自:http://yixiangongzhu.blog.163.com/blog/static/19736320320111123111753465/

msdn上說dwRate/dwScale纔是播放速率。   
  視頻中每秒播放的幀數可能不是整數,比如可能是29.97等,   
  注意到在AVISTREAMINFO結構中,所以屬性都是整型變量表示的,所以小數只能  
  用兩個整數相除得到,這樣就需要用兩個整數(dwRate和dwScale)來得到播放速率。   
  比如速率是29.97,那麼可以用dwRate=2997,dwScale=100得到   
  如果速率是29.9,那麼可以用dwRate=299,dwScale=10得到

在avi文件中包含有AviMainHeader,AviStreamHeader等頭部信息,其中有以下幾個字段:Start、Length、Scale、Rate,有資料中介紹:

InAviMainHeader:

The dwStart and dwLength fields specify the starting time ofthe AVI file and the length of the file. The units are defined bydwRate and dwScale. ThedwStart field is usually set to zero.

The dwScale and dwRate fields are used to specify the generaltime scale that the file will use. In addition to this time scale, each streamcan have its own time scale. The time scale in samples per second is determinedby dividing dwRate by dwScale.

InAviStreamHeader:

dwScale isused together with dwRate to specify the time scale that thisstream will use.

Dividing dwRate by dwScale gives the number of samples persecond.

Forvideo streams, this rate should be the frame rate.

Foraudio streams, this rate should correspond to the time needed fornBlockAlign bytes of audio, which for PCM audiosimply reduces to the sample rate.

 

爲了理解,我們拿一個實際的avi文件來分析一下:

1.VideoAviStreamHeader:Length=4500、Scale=1、Rate=25,因此此文件視頻幀率爲25/1=25,可得:視頻時長:4500/25=180秒。這幾個數字容易理解。

2.AudioAviStreamHeader:Length=2812、Scale=16000、Rate=2,初看一頭霧水,反覆讀資料、仔細分析才明白:對於PCMaudio,nBlockAlign與dwSampleSize相等,爲2,及每個音頻採樣爲兩個字節,每個音頻幀的大小爲1024B,而dwRate/dwScale即爲採樣率:16000/2=8000,因此,音頻時長:

(2812* 1024 / 2) / (16000 / 2) = 179.968秒。

 

 

H264獲取SPS與PPS

2012年09月20日 ⁄ 多媒體技術音視頻編解碼 ⁄ 共 3678字 ⁄ 暫無評論 ⁄ 被圍觀 1,449 views+

在用Android手機進行h264硬編碼的時候如果要進行視頻流的實時傳輸,就需要知道視頻流的SequenceParameter Sets (SPS) 和Picture Parameter Set (PPS)。

今天算是看明白如何獲取SPS和PPS,在這裏記錄下來,希望有需要的朋友可以在這裏獲取到一些些的幫助。

首先說一下大前提,我設置的視頻錄製參數爲:

mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

爲了讓大家更加明白,我先貼出avcC的數據結構:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

aligned(8) class AVCDecoderConfigurationRecord {
   unsigned int(8) configurationVersion = 1;
   
unsigned int(8) AVCProfileIndication;
   unsigned int(8) profile_compatibility;
   unsigned int(8) AVCLevelIndication;

   bit(6) reserved = '111111'b;
   unsigned int(2) lengthSizeMinusOne;

   bit(3) reserved = '111'b;
   unsigned int(5) numOfSequenceParameterSets;
   for (i=0; i< numOfSequenceParameterSets; i++) {
      unsigned int(16) sequenceParameterSetLength ;
      bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
   }

   unsigned int(8) numOfPictureParameterSets;
   for (i=0; i< numOfPictureParameterSets; i++) {
      unsigned int(16) pictureParameterSetLength;
      bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
   }
}

 

 

ok,數據結構貼出來了,我再貼出錄製的3gp輸出類型的h264碼流片斷。

陰影部分就是avcC的全部數據了。

其中: 0x61 0x76 0x63 0x43 就是字符avcC

0x01 是configurationVersion

0x42 是AVCProfileIndication

0x00 是profile_compatibility

0x1F是AVCLevelIndication

0xFF 是6bit的reserved 和2bit的lengthSizeMinusOne

0xE1 是3bit的reserved 和5bit的numOfSequenceParameterSets

0x00 0x09是sps的長度爲9個字節。

故SPS的內容爲接下來的9個字節:67 42 00 1f e9 02 c1 2c 80

接下來的:01爲numOfPictureParameterSets

0x00和0x04是pps的長度爲4個字節。

故PPS的內容爲接下來的4個字節:68 ce 06 f2

 

通過這段數據片斷,就可以獲取到SPS和PPS了。

下面我將貼上用java代碼獲取輸出格式爲3gp的h264碼流的SPS與PPS代碼:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

package cn.edu.xmu.zgy;

import java.
io.File;
import java.
io.FileInputStream;
import java.
io.IOException;

public class ObtainSPSAndPPS {

    public void getSPSAndPPS(String fileName) throws IOException {
        File file = new File(fileName);
        FileInputStream fis = new FileInputStream(file);

        int fileLength = (int) file.length();
        byte[]
 fileData = new byte[fileLength];
        fis.read(fileData);

        // 'a'=0x61, 'v'=0x76, 'c'=0x63, 'C'=0x43
        byte[]
 avcC = new byte[] { 0x61, 0x76, 0x63, 0x43 };

        // avcC的起始位置
        
int avcRecord = 0;
        
for (int ix = 0; ix < fileLength; ++ix) {
            if (fileData[ix] == avcC[0] && fileData[ix + 1] == avcC[1]
                    &&
 fileData[ix + 2] == avcC[2]
                    &&
 fileData[ix + 3] == avcC[3]) {
                // 找到avcC,則記錄avcRecord起始位置,然後退出循環。
                avcRecord 
= ix + 4;
                
break;
            }

        }
        if (0 == avcRecord) {
            System.out.println("沒有找到avcC,請檢查文件格式是否正確");
            
return;
        }


        // 7的目的是爲了跳過
        
// (1)8字節的 configurationVersion
        
// (2)8字節的 AVCProfileIndication
        
// (3)8字節的 profile_compatibility
        
// (4)8 字節的 AVCLevelIndication
        
// (5)6 bit reserved
        
// (6)2 bit lengthSizeMinusOne
        
// (7)3 bit reserved
        
// (8)5 bit numOfSequenceParameterSets
        
// 6個字節,然後到達sequenceParameterSetLength的位置
        
int spsStartPos = avcRecord + 6;
        byte[]
 spsbt = new byte[] { fileData[spsStartPos],
                fileData[spsStartPos + 1] };
        int spsLength = bytes2Int(spsbt);
        byte[] SPS = new byte[spsLength];
        // 跳過2個字節的 sequenceParameterSetLength
        spsStartPos +
= 2;
        System.
arraycopy(fileData, spsStartPos, SPS, 0, spsLength);
        printResult("SPS", SPS, spsLength);

        // 底下部分爲獲取PPS
        
// spsStartPos + spsLength 可以跳到pps位置
        
// 再加1的目的是跳過1字節的 numOfPictureParameterSets
        
int ppsStartPos = spsStartPos + spsLength + 1;
        byte[]
 ppsbt = new byte[] { fileData[ppsStartPos],
                fileData[ppsStartPos + 1] };
        int ppsLength = bytes2Int(ppsbt);
        byte[] PPS = new byte[ppsLength];
        ppsStartPos += 2;
        System.
arraycopy(fileData, ppsStartPos, PPS, 0, ppsLength);
        printResult("PPS", PPS, ppsLength);
    }

    private int bytes2Int(byte[] bt) {
        int ret = bt[0];
        ret 
<<= 8;
        ret |
= bt[1];
        
return ret;
    }

    private void printResult(String type, byte[] bt, int len) {
        System.out.println(type + "長度爲:" + len);
        String cont = type + "的內容爲:";
        System.
out.print(cont);
        for (int ix = 0; ix < len; ++ix) {
            System.out.printf("%02x ", bt[ix]);
        }
        System.out.println("\n----------");
    }


    public static void main(String[] args) throws IOException {
        new ObtainSPSAndPPS().getSPSAndPPS("c:\\zgy.h264");
    }

}

 

 

運行結果如下:

 

1
2
3
4
5
6

SPS長度爲:9
SPS
的內容爲:67 42 00 1f e9 02 c1 2c 80 
----------

PPS長度爲:4
PPS
的內容爲:68 ce 06 f2 
----------

 

發佈了58 篇原創文章 · 獲贊 8 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章