AVI文件在opencore框架下的解析

AVI文件在opencore框架下的解析

    參考相關文檔及opencore中pv_avifile_parse等實現,分析opencore下AVI文件解析的實現過程。

1. AVI容器介紹
    AVI是微軟1992年推出用於對抗蘋果Quicktime的技術,儘管國際學術界公認AVI已經屬於被淘汰的技術,但是由於windows的通用性,和簡單易懂的開發API,還在被廣泛使用。
  AVI的文件結構分爲頭部、主體和索引三部分。主體中圖像數據和聲音數據是交互存放的。從尾部的索引可以索引跳到自己想放的位置。AVI本身只是提供了這麼一個框架,內部的圖像數據和聲音順據格式可以是任意的編碼形式。因爲索引放在了文件尾部,所以在播internet流媒體時已屬力不從心。很簡單的例子,從網絡上下載的片子,如果沒有下載完成,是很難正常播放出來。
另外一個問題是AVI對高碼率VBR音頻文件支持不好。VBR全稱是Variable BitRate,就是動態比特率,可以根據當前的需要定義不同的比特率,避免了浪費,並且提高了利用率。隨之問題也就來了,因爲容器裏的圖像和聲音是分開的,所以播放時需要一個圖像和聲音的同步過程,如果CBR音軌的話因爲碼率是定值,同步不成爲問題,可是VBR音軌是不斷的在變換,而AVI沒有時間戳去讓VBR音軌和圖像同步,這樣就會產生圖像聲音不同步的問題。
兼容的視頻編碼:MPEG-2、MPEG-4、MPEG-4 H.264、VC-1(支持不太好)、VC-1;兼容的音頻編碼:Linear PCM、DTS-HD DTS、Dolby Digital、AC3、Dolby Digital Plus、Dolby TrueHD、DTS Digital Surround。

2 . AVI文件組織方式
    AVI(Audio Video Interleaved的縮寫)是一種RIFF(Resource Interchange File Format的縮寫)文件格式,多用於音視頻捕捉、編輯、回放等應用程序中。通常情況下,一個AVI文件可以包含多個不同類型的媒體流(典型的情況下有一個音頻流和一個視頻流,現在有非標準插件可加入最多兩個音頻軌道),不過含有單一音頻流或單一視頻流的AVI文件也是合法的。AVI可以算是Windows操作系統上最基本的、也是最常用的一種媒體文件格式。
    RIFF格式是一種樹狀的結構,其基本組成單元爲LIST和CHUNK,分別如樹的節點和葉子。RIFF格式也類似windows文件系統的組織形式,windows文件系統有目錄和文件,分別對應RIFF中的LIST和CHUNK。Windows文件系統中的目錄可以包含子目錄和文件,而文件是保存數據的基本單元,RIFF也使用了這樣的結構。在RIFF文件中,數據保存的基本單元是CHUNK,可用於保存音視頻數據或者一些參數信息,LIST相當於文件系統的目錄,可以包含多個CHUNK或者多個LIST。
    AVI文件的兩種原子類型:
(1)LIST
    typedef struct{
        DWORD dwList;
        DWORD dwSize;
        DWORD dwFourCC;
        BYTE  data[dwSize -4];  //contain lists and chunks
    }LIST;
(2)CHUNK
    typedef struct{
        DWORD dwFourCC;
        DWORD dwSize;
        BYTE  data[dwSize ];   //contain headers or video/audio data
    }CHUNK;
    用LIST類型的只有 ‘RIFF’或’LIST’,類似樹枝,而CHUNK類似葉子。下圖中ID爲RIFF、hdrl、strl、odml、INFO、movi均爲LIST類型,hdrl LST塊定義AVI文件的數據格式,movi LIST包含音視頻序列數據,idx1 CHUNK存放索引數據,該塊可選。

AVI Tree


文件結構圖
   
2.1 RIFF LIST結構
    dwList爲’RIFF’,dwFourCC爲’AVI’,當前不支持AVIX(open DML AVI),這是一個LIST,後續的filedata部分包含信息塊、數據塊和索引塊,可以是LIST也可以是CHUNK,需要遞歸解析(注:Pv_avifile_parse.cpp中ParseFile函數採用循環解析所有的第一層LIST,但沒有解析LIST中具體內容)。在這個解析過程中需要先解析出第一個chunktype爲RIFF和第二個chunktype必須爲AVI(與MPEG4Extractor.cpp類似,用壓棧保存chunktype),後續的fileData採用遞歸解析。
   
2.2 HDRL LIST信息塊
    dwList爲’LIST’,dwFourCC爲’hdrl’,data部分包含了avih CHUNK和其他strl LIST,需要遞歸解析。Size部分是指從dwFourCC開始的字段大小。
2.2.1 AVIH CHUNK
    AVIH CHUNK中data字段結構如下:

AVIH Data
    Opencore 的PV_avifile_header.cpp代碼中解析了上述所有字段iMicroSecPerFrame、iMaxBytesPerSec、iPadding、iFlags、iTotalFrames、iInitialFrames等,這些字段保存在PVAviFileMainHeaderStruct結構體中,其中iFlags字段用對應位表示5個標誌:AVIF_COPYRIGHTED、AVIF_HASINDEX、AVIF_ISINTERLEAVED、AVIF_MUSTUSEINDEX、AVIF_WASCAPTUREFILE。

2.2.2 STRL LIST
    這個LIST部分包括以下幾個CHUNK:STRH、STRF、STRD、STRN、JUNK。iStreamListSize保存STRL LIST的data部分長度。
2.2.2.1 STRH CHUNK
    STRH CHUNK中data字段結構如下:
   
   
    注意:wPriority和wLanguage並不是四字節存儲,解析時注意偏移位置,dWFlags有兩種取值:AVISF_VIDEO_PALCHANGES、AVISF_DISABLED。
    rcFrame包括上left、top、right、bottom四個值,分別用short int(佔兩個字節)保存。
    幀率的計算:SamplingRate(samples/second)= dwRate/dwScale。

2.2.2.2 STRF CHUNK
    Strf結構與strh的fccType定義的類型有關。AVIStream::fccType的可能取值有:PV_2_AUDIO、PV_2_VIDEO、MIDI和TEXT。
(1)AVIStream::fccType爲視頻PV_2_VIDEO時,STRF CHUNK的結構爲BITMAPINFO:
    typedef struct
    {
        BitmapInfoHhr  BmiHeader;  //header
        uint32         BmiColorsCount;  //number of RGBQuads actually present
        RGBQuad      BmiColors[MAX_COLOR_TABLE_SIZE];
    } BitMapInfoStruct;
    RGBQuad結構如下:
    typedef struct
    {
        uint8   Blue;
            uint8   Green;
        uint8   Red;
        uint8   Reserved;
    } RGBQuad;
    BitmapInfoHhr結構如下:

    biClrUsed標識了BmiColorsCount,當這個字段值爲0時,需要由biBiBitCount字段(每幀的比特數)決定color table的大小(BmiColorsCount):
    biBiBitCount =1時,color table size = 2^1;
    biBiBitCount = 2時,color table size = 2^2;
    biBiBitCount = 4時,color table size = 2^4;
    biBiBitCount = 8時,color table size = 2^8;
    biBiBitCount = 16/32時,color table size由biCompression的值決定:
        BiCompression= BI_BITFIELDS,color table size = 3;
        BiCompression= BI_ALPHABITFIELDS,color table size = 4;
        BiCompression= BI_RGB,color table size = 0;
    biBiBitCount = 24時,color table size = 0;
    BmiColors數組用來存放color table entry,詳見ParseStreamFormat函數中的解析過程。

(2) AVIStream::fccType爲音頻PV_2_AUDIO時,STRF CHUNK結構爲WAVEFORMAT:


2.2.2.3 STRD CHUNK(可選)
    strd chunk爲可選部分,緊跟在strf chunk後,保存編解碼器需要的一些配置信息,文件規範中沒有具體說明,參考opencore代碼,包含size和data部分。
    DWORD iCodecSpecificHdrDataSize;  //大小
    BYTE   ipCodecSpecificHdrData[DataSize];    //數據

2.2.2.4 STRN CHUNK(可選)
    DWORD strnSz;  //大小
    CHAR   iStreamName [MAX_STRN_SZ];    //數據
    保存流的名字,當strnSz<MAX_STRN_SZ時,iStreamName保存strnSz個BYTE;當strnSz>MAX_STRN_SZ,保存MAX_STRN_SZ 個BYTE。PVAviFileStreamlist函數中臨時分配的空間strn似乎多此一舉。

2.2.2.5 JUNK CHUNK
    爲了字節對齊填充的字段,不解析。

2.2.3 JUNK LIST
    爲了字節對齊填充的字段,可不解析。

2.3 MOVI LIST數據塊
    dwList爲’LIST’,dwFourCC爲’movi’。Size部分是指從dwFourCC開始的字段大小。movi列表保存的是真正的媒體流數據,其數據組織方式有兩種。可以將數據塊直接嵌在‘movi’列表裏面,也可以將幾個數據塊分組成一個‘rec ’列表後再編排進‘movi’列表(讀取數據時最好將整個rec讀出)。每個數據塊使用了一個四字符碼來表徵它的類型,這個四字符碼由2個字節的類型碼和2個字節的流編號組成。標準的類型碼定義如下:‘db’(非壓縮視頻幀)、‘dc’(壓縮視頻幀)、‘pc’(改用新的調色板)、‘wb’(音縮視頻)。比如第一個流(Stream 0)是音頻,則表徵音頻數據塊的四字符碼爲‘00wb’;第二個流(Stream 1)是視頻,則表徵視頻數據塊的四字符碼爲‘00db’或‘00dc’。對於視頻數據來說,在AVI數據序列中間還可以定義一個新的調色板,每個改變的調色板數據塊用‘xxpc’來表徵,新的調色板使用一個數據結構AVIPALCHANGE來定義。(注意:如果一個流的調色板中途可能改變,應在這個流格式的描述中,也就是PVAviFileStreamHeaderStruct結構的dwFlags中包含一個AVISF_VIDEO_PALCHANGES標記)。另外,文字流數據塊可以使用隨意的類型碼錶徵。

    MOVI LIST的data部分每一個子chunk存放的是sample信息,sample所屬的stream no可由indx的字節/16計算而來。
   
2.4 IDX1 LIST索引塊(非必需)
    dwList爲’LIST’,dwFourCC爲’idx1’。Size部分是指從dwFourCC開始的字段大小。結構如下(如下爲AVI1.0的結構,AVI2.0不同):
   
    索引塊包含數據塊在文件中的位置索引,能提高avi文件的讀寫速度,其中存放着一組AVIINDEXENTRY結構數據,這個塊並不是必需的,也許不存在。dwChunkId包含了兩字節的stream number(該sample所屬的stream號)和兩個字節的封裝類型碼,因此這個字段解析上有一個實現細節,dwChunkId值類似‘00dc’:十六進制30 30 64 63(轉成十進制ASCII碼爲48 48 100 99對應字符“00dc”),chunkId前兩個字節包含了streamno,並且用ASCII碼錶示,如存放的是’0’和’0’字符,最後轉成十進制爲0*10+0=0(PV_atoi),“01wb”(30 31 77 62)前兩個字節轉成十進制就是1。
    iIndexTable是streams數組,每一個成員又是IndxTblVector類型的,即iIndexTable[aStreamNo]是一個IndxTblVector類型,IndxTblVector的每個成員是IdxTblType類型。每個iIndexTable[aStreamNo]又是一個數組,每個成員是一個sample。相當於二維數組,第一維大小sizeof(iIndexTable)= stream numbers,sizeof(IndxTblVector)=一個stream中的sample數,iIndexTable中的最小信息單元是一個sample。

2.5 INFO LIST
    dwList爲’LIST’,dwFourCC爲’info’,Size部分是指從dwFourCC開始的字段大小。Opencore代碼中沒有解析這一部分。

2.6 JUNK LIST
    爲了字節對齊填充的字段,可不解析。

2.7 關鍵計算過程
2.7.1 計算時間戳
    參考GetNextMediaSample中的計算部分
    音頻根據幀率計算:該幀大小arSize/每幀所佔的字節數=samplecount
    iTimeStampAudio = iTimeStampAudio+(sampleCount * 1000) / samplingRate
    視頻根據幀間隔計算:frameDurationInms =(iMainHeader.iMicroSecPerFrame) / 1000
    arTimeStamp = (iStreamSampleCount[aStreamNo] * (frameDurationInms))

2.7.2 查找sample
    (1) 確定時間,相對於媒體時間座標系統;
    (2) 查找對應該時間戳的sample序號(需要在解析的時候保存到iIndexTable表,如果不存在該索引表,需要在解析movi chunk時記錄下來);
    (3) 根據sample號查找該sample在文件中位移和大小(索引表iIndexTable或movi chunk);

2.7.3 查找關鍵幀
必須要用Idx1中的標誌位??

3 Opencore代碼分析
    分析代碼:Pv_avixxx.cpp、Pvmi_mio_avi_wav_file.cpp
3.1 數據結構存儲方式

數據結構圖
    PVAviParser包括PVAviFileHeader和PVAviFileIdxChunk,包括HDRL LIST、IDX1 CHUNK。
    PVAviFileHeader類包含PVAviFileStreamlist類型的vector—>iStreamList列表,一個strl list對應一個PVAviFileStreamlist對象,hdrl list中可以包含多個strl list(流)。
    PVAviFileStreamlist(HDRL LIST的data部分)包含PVAviFileStreamHeaderStruct(avih chunk)和PVAviFileStreamFormatStruct,PVAviFileStreamFormatStruct存儲的是strl list解析出來的data數據,其中的strf chunk結構取決於strh的fccType類型,對應有BitMapInfoStrunct和WaveFromatExStruct兩種,因此在PVAviFileStreamFormatStruct中iVidBitMapInfo和iAudWaveFormatEx是以共用體定義的。
    爲了查找到sample等信息,這裏有幾個重要的字段:
    Oscl_Vector < uint32,OsclMemAllocator >  iStreamCount;   文件中的stream數
    Oscl_Vector < uint32,OsclMemAllocator >  iStreamSampleCount; 該數值從0開始,表示當前該stream中已讀到的sample數,該字段隨着讀取文件的過程更新,因此該stream下次要讀取的sample號 = 已經讀取的sample數。
    //stores current offset if index table is not present
uint32  iSampleOffset;                    當前要讀取的最新sample位移
//store latest sample offset if index table is not present
    Oscl_Vector < uint32,OsclMemAllocator >  iStreamSampleOffset;   鏈表,存放每個stream下次要讀取的sample位移(位移相對文件開始,如果stream0的sample1已經讀取,那麼存放的是sample2的位移)

3.2 過程分析

解析過程圖
3.2.1 文件解析入口
    Pv_avifile_parse.cpp中ParseFile()函數採用循環解析所有的第一層LIST,同時遞歸解析子LIST或CHUNK。在這個過程中保存了以下信息:iFileSize (文件大小:從AVI開始的部分)、iHeaderChunkSize(hdrl list data部分的長度)、ipFileHeader(hdrl list的數據部分指針)、iMovieChunkSize(movi list data部分的長度)、iMovieChunkStartOffset(movi chunk在文件中的偏移,第一幀偏移,當indx table不存在時使用)、iSampleOffset(= iMovieChunkStartOffset,保存當前位移,當indx table不存在時使用)、iStreamCount、iStreamSampleCount、iIndxChunkSize、ipIdxChunk(Idx的數據部分指針) 、iIdxChunkPresent,遞歸解析PVAviFileHeader :OSCL_NEW(PVAviFileHeader..)、PVAviFileIdxChunk :OSCL_NEW(PVAviFileIdxChunk..)。

3.2.2 解析HDRL  LIST
    PVAviFileHeader解析HDRL LIST的數據部分,從AVIH Chunk開始,過程與ParseFile類似,調用ParseMainHeader解析AVIH Chunk,調用OSCL_NEW(PVAviFileStreamlist,..)遞歸解析STRL,JUNK部分可不解析。
    ParseMainHeader()解析AVIH Chunk,具體解析字段內容見2.2.1。
    PVAviFileStreamlist()解析STRL LIST,過程與PVAviFileHeader和ParseFile類似。ParseStreamHeader解析STRH Data部分,具體字段參考2.2.2.1。ParseStreamFormat解析STRF部分,strf結構與strh fccType定義的類型相關,參考2.2.2.2。

3.2.3 解析IDX1 CHUNK
    PVAviFileIdxChunk解析IDX1 CHUNK,類似二維數組,第一維是stream,第二維是stream中的sample信息。

3.3 內部接口、對外接口及關鍵函數實現
入口:Pv_avifile.cpp
模塊內部接口:
3.3.1 Pv_avifile_streamlist.cpp中的接口
3.3.1.1獲取指定流的音頻格式 GetAudioFormat(uint32 aStreamNo) -> GetAudioFormat()
    HDRL LIST解析後保存了streamlist(PVAviFileStreamlist類型),通過strf chunk讀取WaveFormatExStruct中的FormatTag字段。
3.3.1.2 獲取指定流的聲道數GetNumAudioChannels(uint32 aStreamNo)-> GetNumAudioChannels()。
    HDRL LIST解析後保存了streamlist(PVAviFileStreamlist類型),通過strf chunk讀取WaveFormatExStruct中的Channels字段。

3.3.2 Pv_avifile_streamlist.cpp中的接口,提供PVAviFileStreamlist中的相關信息
3.3.2.1獲取當前流的類型GetStreamMimeType(),由iStreamTypeFCC決定,有四種類型:AUDS、VIDS、MIDS、TXTS。
3.3.2.2 GetHandlerType(uint8* aHdlr, uint32& aSize)獲取strh中的fccHandler。
3.3.2.3 GetBitsPerSample()獲取sample的bit數。
3.3.2.4 GetAudioFormat()獲取音頻格式,被PVAviFileHeader::tAudioFormat(uint32 aStreamNo)調用。
3.3.2.5 GetNumAudioChannels()獲取聲道數,被PVAviFileHeader::tNumAudioChannels(uint32 aStreamNo)調用。
3.3.2.6 GetVideoWidth()獲取視頻寬度。
3.3.2.7 GetVideoHeight()獲取視頻高度。
3.3.2.8 GetFormatSpecificInfo獲取strf chunk解析的內容。
3.3.2.9 GetCodecSpecificData獲取解碼信息。

3.3.3 Pv_avifile_parser_utils.cpp 提供模塊內公共函數
3.3.3.1 ReadNextChunkType讀取下一個chunk的類型。
3.3.3.2 read32 讀取四個字節,涉及到字節序轉換。
3.3.3.3 read8讀取一個字節,不考慮字節序。
3.3.3.4 read16讀取兩個字節,考慮字節序轉換。
3.3.3.5 GetStreamNumber 將前兩個字節存放的ASCII碼值轉換成十進制

模塊的對外接口:(Pv_avifile.h)
3.3.4 Pv_avifile_parser.cpp提供讀幀的對外接口
    Pv_avifile_parse.h和Pv_avifile.h中有相關說明
3.3.4.1 PVAviFileParser::GetNextMediaSample(uint32& arStreamNo, uint8* aBuffer,
                                    uint32& arSize, uint32& arTimeStamp)
    從movi chunk中按順序讀取幀信息,arStreamNo表示當前sample所屬的stream number,aBuffer返回media sample buffer,size是返回的sample大小,arTimeStamp是sample的時間戳信息。
    具體實現過程:iSampleOffset從當前位移讀取sample,CurrOff表示上次讀取的sample位移。

3.3.4.2 PVAviFileParser::GetNextStreamMediaSample(uint32 aStreamNo, uint8* aBuffer, uint32& arSize, uint32& arTimeStamp) 獲取stream中的下一個sample的buffer、大小和時間戳信息(iStreamSampleCount[aStreamNo]表示當前stream中要讀取的下一個sample號,iStreamSampleOffset[aStreamNo]表示當前stream中要讀取的下一個sample的位移)
    具體實現過程:不存在indx時,直接通過movi chunk的起始位移、該stream最新的sample位移(即下一次要讀取的sample位移)等信息進行計算;存在indx時,從ipIdxChunk中存放的每個sample信息獲取。
問題:stream中的sample是連續存放的麼?各個stream的sample之間應該是交錯存放的
    iIndexTable表中每一stream的sample是按序存放的,在movi chunk中同一stream的sample也是按時間順序存放的,但其中夾雜着其他stream的sample信息,總的而言,按時間戳順序存放。

3.3.4.3 PVAviFileParser::GetNextStreamSampleInfo(uint32 aStreamNo, uint32& arSize, uint32& arOffset)獲取stream中的下一次要讀取的sample位移和大小(以iStreamSampleOffset[aStreamNo]決定的位移爲基準進行查找)
    具體實現過程:不存在indx chunk,直接返回失敗;存在indx chunk時,從ipIdxChunk中獲取位移和大小,位移有兩種情況:一是相對於文件開始,另一種是相對movi開始處的位移。ipIdxChunk->GetOffset(aStreamNo, iStreamSampleCount[aStreamNo])

4 參考資料
【1】http://blog.csdn.net/njuitjf/archive/2010/06/19/5680632.aspx
【2】http://www.jmcgowan.com/avi.html
【3】http://msdn.microsoft.com/en-us/library/ms779636.aspx
【4】http://msdn.microsoft.com/en-us/library/dd756808(v=VS.85).aspx
【5】http://pvdtools.sourceforge.net/aviformat.txt
【6】http://blog.csdn.net/ydfy6/archive/2009/09/18/4567230.aspx
【7】http://www.featheast.com/it/video-on-the-web-html5
【8】http://www.codesoso.com/default.aspx
【9】http://soft.cnzer.cn/view-17836-3.html
【10】http://doxygen.reactos.org/d1/dc8/avifile_8c_source.html#l00203
    其他PDF文檔在smb://192.168.9.200/ds/lei.yi/avi下
AVI spec here
http://www.wotsit.org/list.asp?page=2&fc=0&search=&al=
AVI player implementations
http://sourceforge.net/project/showf...group_id=91593
http://avifile.sourceforge.net/
http://freshmeat.net/
http://www.topshareware.com/avi-parser/downloads/1.htm
http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/4a284338-4809-4cdd-8cbd-50b4f440fdde/
http://doxygen.reactos.org/d1/dc8/avifile_8c_source.html
ffmpeg

5 工具
    RIFFSpot.exe 該工具不能具體解析movi list中的內容,只能粗略解析文件結構
    Linux下的ghex2顯示文件的十六進制信息  

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