gstreamer插件調用ffmpeg 詳解

Gstreamer調用FFMPEG解析
----Gstreamer 是如何操作ffmpeg的,以及ffmpeg是如何demux的(AVI 容器格式爲例)


AVI容器格式較爲簡單,所以這裏用AVI爲例,分析gstreamer如何通過ffmpeg來對多媒體文件demux,獲得原始數據流。


1. Gstreamer,ffmpeg, OMX框架關係

 
圖一,框架



Gstreamer通過gst-ffmpeg插件,來管理文件container的demux,gst-ffmpeg通過ffmpeg的libavformat來具體實現對各種文件容器的demux。


1, 主循環建立在gst-ffmpeg插件中, gst_ffmpegdemux_loop 
2,gst-ffmpeg調用ffmpeg的API av_read_frame , 來讀取一個完整的幀數據 
3,ffmpeg 在av_read_frame內部,完成demux工作 
4,文件系統的讀操作,由ffmpeg調用gstfilesrc插件的read來實現 
5,一幀數據讀出以後,由gst-ffmpeg利用gstreamer的pad push 機制,送到gst-OMX
6,  pad push 到GST-OMX後,立即返回。OMX實質上採用異步的方式,送到解碼器 


2. Gstreamer調用Ffmpeg的調用鏈
下面圖二在圖一的基礎上展示了各插件各模塊之間的具體調用關係,就不用語言敘述了,對着代碼看吧。。。
 

圖二,各模塊調用鏈



3. Avi容器格式介紹
AVI格式是音頻視頻交錯(Audio Video Interleaved)的英文縮寫,它是Microsoft公司開發的一種符合RIFF文件規範的數字音頻與視頻文件格式,原先用於Microsoft Video for Windows (簡稱VFW)環境,現在已被Windows 95/98、OS/2等多數操作系統直接支持。AVI格式允許視頻和音頻交錯在一起同步播放,支持256色和RLE壓縮,但AVI文件並未限定壓縮標準,因此,AVI文件格式只是作爲控制界面上的標準,不具有兼容性,用不同壓縮算法生成的AVI文件,必須使用相應的解壓縮算法才能播放出來。常用的AVI播放驅動程序,主要是Microsoft Video for Windows或Windows 95/98中的Video 1,以及Intel公司的Indeo Video。

  在介紹AVI文件前,我們要先來看看RIFF文件結構。AVI文件採用的是RIFF文件結構方式,RIFF(Resource Interchange File Format,資源互換文件格式)是微軟公司定義的一種用於管理windows環境中多媒體數據的文件格式,波形音頻wave,MIDI和數字視頻AVI都採用這種格式存儲。構造RIFF文件的基本單元叫做數據塊(Chunk),每個數據塊包含3個部分,

  1、4字節的數據塊標記(或者叫做數據塊的ID)
  2、數據塊的大小
  3、數據

  整個RIFF文件可以看成一個數據塊,其數據塊ID爲RIFF,稱爲RIFF塊。一個RIFF文件中只允許存在一個RIFF塊。RIFF塊中包含一系列的子塊,其中有一種字塊的ID爲"LIST",稱爲LIST,LIST塊中可以再包含一系列的子塊,但除了LIST塊外的其他所有的子塊都不能再包含子塊。

  RIFF和LIST塊分別比普通的數據塊多一個被稱爲形式類型(Form Type)和列表類型(List Type)的數據域,其組成如下: 

  1、4字節的數據塊標記(Chunk ID)
  2、數據塊的大小
  3、4字節的形式類型或者列表類型
  4、數據

  下面我們看看AVI文件的結構。AVI文件是目前使用的最複雜的RIFF文件,它能同時存儲同步表現的音頻視頻數據。AVI的RIFF塊的形式類型是AVI,它包含3個子塊,如下所述:

  1、信息塊,一個ID爲"hdrl"的LIST塊,定義AVI文件的數據格式。
  2、數據塊,一個ID爲 "movi"的LIST塊,包含AVI的音視頻序列數據。
  3、索引塊,ID爲 "idxl"的子塊,定義 "movi"LIST塊的索引數據,是可選塊。

  AVI文件的結構如下圖所示,下面將具體介紹AVI文件的各子塊構造。


  1、信息塊,信息塊包含兩個子塊,即一個ID爲 avih 的子塊和一個ID 爲 strl 的LIST塊。
 0 && image.height<0){if(image.width<=510){this.width=510;this.height=image.height*510/image.width;}}" border=0<

  avih"子塊的內容可由如下的結構定義:
typedef struct 
{
 DWORD dwMicroSecPerFrame ; //顯示每楨所需的時間ns,定義avi的顯示速率
 DWORD dwMaxBytesPerSec; // 最大的數據傳輸率
 DWORD dwPaddingGranularity; //記錄塊的長度需爲此值的倍數,通常是2048
 DWORD dwFlages; //AVI文件的特殊屬性,如是否包含索引塊,音視頻數據是否交叉存儲
 DWORD dwTotalFrame; //文件中的總楨數
 DWORD dwInitialFrames; //說明在開始播放前需要多少楨
 DWORD dwStreams; //文件中包含的數據流種類
 DWORD dwSuggestedBufferSize; //建議使用的緩衝區的大小,
 //通常爲存儲一楨圖像以及同步聲音所需要的數據之和
 DWORD dwWidth; //圖像寬
 DWORD dwHeight; //圖像高
 DWORD dwReserved[4]; //保留值
}MainAVIHeader;

  "strl" LIST塊用於記錄AVI數據流,每一種數據流都在該LIST塊中佔有3個子塊,他們的ID分別是"strh","strf", "strd";
"strh"子塊由如下結構定義。
typedef struct 
{
 FOURCC fccType; //4字節,表示數據流的種類 vids 表示視頻數據流
 //auds 音頻數據流
 FOURCC fccHandler;//4字節 ,表示數據流解壓縮的驅動程序代號
 DWORD dwFlags; //數據流屬性
 WORD wPriority; //此數據流的播放優先級
 WORD wLanguage; //音頻的語言代號
 DWORD dwInitalFrames;//說明在開始播放前需要多少楨
 DWORD dwScale; //數據量,視頻每楨的大小或者音頻的採樣大小
 DWORD dwRate; //dwScale /dwRate = 每秒的採樣數
 DWORD dwStart; //數據流開始播放的位置,以dwScale爲單位
 DWORD dwLength; //數據流的數據量,以dwScale爲單位
 DWORD dwSuggestedBufferSize; //建議緩衝區的大小
 DWORD dwQuality; //解壓縮質量參數,值越大,質量越好
 DWORD dwSampleSize; //音頻的採樣大小
 RECT rcFrame; //視頻圖像所佔的矩形
}AVIStreamHeader;

  "strf"子塊緊跟在"strh"子塊之後,其結構視"strh"子塊的類型而定,如下所述;如果 strh子塊是視頻數據流,則 strf子塊的內容是一個與windows設備無關位圖的BIMAPINFO結構,如下:
typedef struct tagBITMAPINFO
{
 BITMAPINFOHEADER bmiHeader;
 RGBQUAD bmiColors[1]; //顏色表
}BITMAPINFO;


typedef struct tagBITMAPINFOHEADER
{
 DWORD biSize;
 LONG biWidth;
 LONG biHeight;
 WORD biPlanes;
 WORD biBitCount;
 DWORD biCompression;
 DWORD biSizeImage;
 LONG biXPelsPerMeter;
 LONG biYPelsPerMeter;
 DWORD biClrUsed;
 DWORD biClrImportant;
}BITMAPINFOHEADER;

  如果 strh子塊是音頻數據流,則strf子塊的內容是一個WAVEFORMAT結構,如下:
typedef struct 
{
 WORD wFormatTag; 
 WORD nChannels; //聲道數
 DWORD nSamplesPerSec; //採樣率
 DWORD nAvgBytesPerSec; //WAVE聲音中每秒的數據量
 WORD nBlockAlign; //數據塊的對齊標誌
 WORD biSize; //此結構的大小
}WAVEFORMAT

  "strd"子塊緊跟在strf子塊後,存儲供壓縮驅動程序使用的參數,不一定存在,也沒有固定的結構。
  "strl" LIST塊定義的AVI數據流依次將 "hdrl " LIST 塊中的數據流頭結構與"movi" LIST塊中的數據聯繫在一起,第一個數據流頭結構用於數據流0,第二個用於數據流1,依次類推。

  數據塊中存儲視頻和音頻數據流,數據可直接存於 "movi" LIST塊中。數據塊中音視頻數據按不同的字塊存放,其結構如下所述,

  音頻字塊
    "##wb"
    Wave 數據流
  視頻子塊中存儲DIB數據,又分爲壓縮或者未壓縮DIB,
    "##db"
    RGB數據流
    "##dc"
  壓縮的圖像數據流

  看到了吧,avi文件的圖像數據可以是壓縮的,和非壓縮格式的。對於壓縮格式來說,也可採用不同的編碼,也許你曾經遇到有些avi沒法識別,就是因爲編碼方式不一樣,如果沒有相應的解碼,你就沒法識別視頻數據。AVI的編碼方式有很多種,比較常見的有 mpeg2,mpeg4,divx等。
索引塊,索引快包含數據塊在文件中的位置索引,能提高avi文件的讀寫速度,其中存放着一組AVIINDEXENTRY結構數據。如下,這個塊並不是必需的,也許不存在。
typedef struct 
{
 DWORD ckid; //記錄數據塊中子塊的標記
 DWORD dwFlags; //表示chid所指子塊的屬性
 DWORD dwChunkOffset; //子塊的相對位置
 DWORD dwChunkLength; //子塊長度
};

(以上AVI介紹的文字來自網絡)

下面圖片是用AVI-mux GUI 工具得來的。可以比對上面文字瞭解AVI的具體格式。






4. Ffmpeg是如何demux AVI的


圖三對圖二中的函數avi_read_packet如何demux AVI 文件進行了具體描述。

圖三,ffmpeg demux AVI文件的流程。



Demux工作主要由兩個函數完成:avi_sync 以及av_get_packet。
我們知道,流數據存在於AVI 容器的"movi" LIST塊中,每個塊有個header,其中存儲了這個塊的流編號,流類型,塊大小。Avi_sync通過讀取並分析這個header,就能得到有效數據的位置和size。
然後由av_get_packet 來負責完成實際數據的讀取,讀取的數據存在AVPacket.data中。
讀取完之後,就可以填充AVPacket結構體的成員。該結構體被帶到上層後,上層程序就知道讀到的數據的屬性。
需要注意的是,avi_sync是avi 容器特有的函數,專門針對avi格式的demux工作。
av_get_packet則是ffmpeg讀取有效數據時的通用函數。適用於多種容器格式。


 


圖四,avi_sync於av_get_packet


Ffmpeg在讀取文件數據時,調用的是avio_read函數。爲了提高對文件的讀速度,avio_read做了優化。如圖五所示。Avio_read讀的數據始終是一個buffer中的數據,如果buffer中有數據,就直接讀取,如果buffer中沒數據,就調用read函數從文件系統中一次性讀取若干字節,暫存到buffer中。然後再從buffer中讀。這樣避免了頻繁小批量的讀文件系統,提高了讀速度。




 
圖五,avio_read 優化

5. 快進快放功能是如何實現的

AVI容器如何實現快進快放功能也需要着重講一下。
Gstreamer在快進的時候,計算偏移量和dts,使用的是自己的gstreamer  time base,得到的dts是基於gstreamer time base的值。在交給ffmpeg的時候,需要轉化成基於ffmpeg 中流自身timebase的dts,然後用這個dts查索引表,得到離這個dts最近的I幀位置,取得I幀,送給OMX。
對AVI文件來說,索引表的建立是在ffmpeg初始化讀取AVI容器頭的時候,通過讀取idxl索引塊來建立的。


 


圖六,快進快放實現流程




 


圖七, seek操作時調用鏈

6. 初始化時鉤子的掛接

圖二中,紅色字體標示出了三處鉤子函數。

(1) s->iformat->read_packet 取決於容器類型,對MVC, AVI,MP4等不同容器類型來說,這裏的掛接函數不一樣。這裏以AVI文件類型爲例,所以掛接了avi_read_packet 。

s->iformat->read_packet(s, pkt)  ----》  avi_read_packet 

 (此鉤子在gst_ffmpegdemux_open--》av_open_input_file---》avformat_open_input掛上)

(2)s->read_packet 在ffmpeg中固定掛接成ffurl_read函數
len = s->read_packet(s->opaque, buf, size)  ---》  ffurl_read   
(此鉤子在gst_ffmpegdemux_open--》av_open_input_file---》avformat_open_input---》init_input---》avio_open2---》ffio_fdopen--》avio_alloc_context--》ffio_init_context--》掛上)

(3)h->prot->url_read 取決於數據來源。對於分別有file,HTTP,RTSP,gstreamer,等不同來源。這裏由於使用gstreamer的filesrc插件來讀數據,所以選擇了gstreamer源。掛接了gst_ffmpegdata_read函數。
h->prot->url_read  ---》  gst_ffmpegdata_read 
 (次鉤子在gst_ffmpegdemux_open--》av_open_input_file---》avformat_open_input---》init_input---》avio_open2---》ffurl_open---》ffurl_alloc---》url_alloc_for_protocol掛上)

這些鉤子都是在gst_ffmpegdemux_open中完成掛接的。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章