數字媒體技術揭祕

如果算上模擬時代,多媒體傳輸也算不上是多麼新鮮的事情。實際上,早在上世紀三十年代,人們便可以在家觀賞奧運賽事:來自柏林現場的活動畫面連同聲音通過電纜或者無線電波被傳送到世界各地1),雖然圖像還不是彩色的,但就質量來說並不見得就比YouTube上NBC的北京2008差。從某種意義上講,數字技術的突飛猛進對多媒體通信的推動並非它能夠在多大程度上提高媒體內容的質量——這方面藝術家們所起到的作用可能會更大——而是它可以令媒體的傳播更便捷、更便宜,於是,如果願意的話,現今每一個人都可以演一出好戲發到世界各個角落裏去吸引眼球,就如上世紀三十年代那位元首所做的一樣。

在模擬時代,最大的一個大難題是如何有效利用帶寬。這裏的帶寬可以理解爲傳輸媒介,對於以電磁信號爲介質的傳輸方式,通常指的是一個頻率範圍。一般情況下每一路電視節目要佔據6-8MHz的無線頻譜或者電纜頻譜,而且即使是在空閒的時候,這些被佔據的頻譜也無法被釋放,有些電視臺只好傳送幾根呆滯的彩條(三十年前出生的人幾乎都有過觀賞彩條的經歷)。相對而言,引入交換機制的電話網看上去會好一點:電信局確然可以保證成千上萬對用戶同時進行通話,可是分給每對用戶的只有可憐兮兮的4KHz帶寬,而且,在雙方沉默無語的時候,這些帶寬並不能夠被挪作他用,結果,人們在講電話的時候總是竭力地尋找話題填滿所有的通話時間,以免因產生的空閒而支付昂貴的電話費用。

上世紀四十年代末,香農提出了關於信息及其傳送的關鍵理論,於是人們發現,對於給定的帶寬,其理論上的傳輸能力要比當時通信系統實際所實現的大得多。然而,在模擬的範疇內,無論是從信源的角度還是從信道的角度,靠近香農極限都是一件相當困難的事情。

不妨以廣播電視爲例,回顧一下先前的人們爲節省帶寬而付出的努力:

  1. 殘留邊帶調製:圖像信號的下邊帶只保留了少部分帶寬(N制中是1.25MHz)。
  2. 亮色混合:由於掃描產生的圖像信號具有某種程度上的週期性,在頻譜上則表現爲以行掃描頻率爲間隔的衆多脈衝,這使得亮度信號和色差信號在同一帶寬範圍內進行疊加成爲可能,條件是它們在頻域相互錯開半個行掃描頻率。換句話說,人們利用一種巧妙的方法在不增加帶寬的前提下把黑白電視升級爲彩色電視。
  3. 隔行掃描:老式的CRT顯示器要求刷新頻率大於50Hz,否則會讓人感覺到閃爍,但人眼感知運動畫面的最低要求僅爲每秒鐘二十餘幀左右,爲了在不增加冗餘的帶寬(亦即不增加幀頻)的前提下消除閃爍現象,人們引入了隔行掃描技術,即通過將一幅圖像拆作相互交錯的兩場來實現二倍於幀頻的刷新頻率。(利用 frame buffer將同一副圖像連續顯示多次其實亦可解決這個問題,可惜在模擬電視時代該方法的實現具有一定的技術難度)。

計算機專業出身的同學可能對上述歷史並不很熟悉,然而不可否認的是,儘管多媒體技術通常被認爲是計算機學科的一個分支,其源起卻在通信領域。在ITU的學僚們開始籌劃將電信網絡改造爲能夠傳送“綜合業務”的多媒體網絡時,計算機還僅僅被認爲是一種計算機器。

最先被數字化的是語音。早在上世紀六十年代,貝爾實驗室的PCM技術便開始被應用於電話網。通過簡單的採樣和量化,這種技術將4KHz帶寬的語音轉換爲64Kbps的數字信息,使之能夠在採用時分複用的電話幹線中傳輸。有趣的是,當時,普通的電話雙絞線反而無法傳輸這種數字語音——64Kbps的數據率對於它們來說太大了。難道數字媒體要求更多的帶寬?並非如此,從香農的理論出發很容易找出問題所在:其一,PCM技術所產生的信息速率大大超出了語音信息本身的熵率——這是種很粗糙的編碼技術;其二,33.6kbps的調製技術遠遠沒有達到雙絞線可以提供的傳輸極限。結論就是,數字化並非就是簡單的採樣和量化,它是一個複雜的信息表示過程:既包括信源編碼——在保證信息可恢復的前提下產生儘量少的數據速率;也包括信道編碼——在給定帶寬的前提下如何承載更大的數據速率。對於多媒體信息來說,前者更是備受關注。譬如,同樣是4KHz的語音,如果採用新的編碼技術如G.723.1,產生的數據速率只有5~6kbps,對於雙絞線來說綽綽有餘。

圖1. NTSC頻譜結構:

圖2. 一個L1載波的頻譜分配:

圖3. 一個DS載波的時隙分配:

視頻面臨的挑戰更大一些,按照4:4:4採樣的N制彩色電視信號產生的數據率高達60.8Mbps,即使採用目前極先進的調製手段——如號稱接近香農極限的DVB-C2@1024QAM,也會將8MHz的帶寬全部喫掉。因此,早期的數字視頻傳輸研究都集中於內容簡單的低分辨率圖像(CIF及QCIF)。ITU是最初的推動者,上世紀八十年代,該組織一直致力於一項徹底改造老式電話網的工程,企圖實現包括用戶接入在內的全數字化網絡,以提供包括數字語音、數字視頻和數據的“綜合業務”。不幸的是,由於新的數字調製方式的出現以及因特網的衝擊,這種被稱作ISDN的網絡很快就過時了,但由其產生的視頻壓縮標準H.261卻開了數字視頻傳輸的先河,成爲後來一系列技術諸如H.263、H.264、RM以及MPEG系列的鼻祖。

開會和制定標準是ITU學僚們的特長,但要坐等他們引導世界進入多媒體時代,恐怕需要相當大的耐心。幸運的是,PC的面世使個人處理多媒體數據成爲可能,哪怕是一臺裝有DOS的286,相信也會比一臺ISDN電話機能給人帶來更大的遐想空間。

1991年制定了第一個多媒體PC的標準:

  • 16 MHz 386SX CPU
  • MB RAM
  • 30 MB hard disk
  • 256-color, 640×480 VGA video card
  • 1x (single speed) CD-ROM drive using no more than 40% of CPU to read, with < 1 second seek time
  • Sound card outputting 22 kHz, 8-bit sound; and inputting 11 kHz, 8-bit sound
  • Windows 3.0 with Multimedia Extensions.

看上去它似乎還做不了什麼,但它將來能。

世界總是如此,多少風光無限者其實早已薄暮西山,多少貌不驚人者卻是在暗自醞釀着爆發的能量,又有多少明眼人可以看得出來?

二、挑戰

通過對模擬音視頻數據採樣、量化得到的原始數字音視頻的數據量龐大無比: 

RGB圖像分辨率 數據量
QCIF(176×144) 76,032 Byte
CIF(352×288) 304,128 Byte
QVGA(320×240) 230,400 Byte
VGA(640×480) 921,600 Byte
SVGA(800×600) 1,440,000 Byte
SD-PAL(720×576) 1,244,160 Byte
SD_NTSC(720×480) 1,036,800 Byte
HD(1280×720) 2,8764,800 Byte
FHD(1920×1080) 6,220,800 Byte

而當前的數字存儲媒介和傳輸信道所能承載的數據速率相當有限:

媒介/傳輸方式 容量/速率
CD-ROM 650M Byte
DVD-ROM 4.7G Byte
Blueray 25G/50G Byte
Voice Modem 33.4 kbps
ISDN 64 kbps
T1 1.544 Mbps
GSM 15 kbps
UTMS 2.8 Mbps

爲了彌補二者的差距,我們需要在竭力降低傳輸代價的前提下,提供給受衆主觀感受上儘可能良好的音視頻信息。這裏面對人類的聽覺和視覺感受系統的研究是非常重要的,以視覺系統(HVS)爲例:人眼能夠識別的分辨率是有限的,而且對水平和垂直方向較其他方向更加敏感,對灰度信息較顏色信息更加敏感,對靜止畫面較活動畫面更加敏感;人眼還會在主觀上放大邊緣區域的對比度;更關注感興趣區域等。

三、傳輸與存儲

3.1 數字媒體流

二進制比特是數字媒體傳輸和存儲的基本形式,每個比特非0即1,任何數字媒體信息必然由若干連續的0或1形成的比特序列來表示。之所以稱之爲流,是由於聲音和視頻等媒體信息總是在時間線上展開的,譬如DVD光盤上的一個電影片段、手機通話過程中的一段語音、或者電腦中的一個MP3文件。

3.1.1 多媒體原始流

原始流的概念來源於ISO的MPEG-2標準2),通常指音頻、視頻或數據編碼器輸出的二進制比特流,也即可以直接作爲解碼器輸入的比特流。原始流是數字媒體傳輸和存儲系統中最基本、最底層的數據單元,而更高級的、面向應用的數據單元都是在原始流的基礎上按照特定協議層層封裝而來的。

3.1.1.1 音頻原始流的結構
3.1.1.2 視頻原始流的結構

目前,常見的視頻編碼基本基於混合編碼框架(詳見4.3.1),因此視頻原始流的結構也大都類似。

視頻等價於時間軸上的一個圖像序列,根據混合編碼方案,每一副圖像都將被切割爲大小相等的方塊,除了某些特殊的應用場景之外,以此爲基本編碼單位的編碼過程按照掃描順序在圖像中自左至右、自上而下進行。在H.265之前的視頻標準中,這一基本編碼單位的大小爲16像素見方,稱爲宏塊;H.265中這一大小得到修正,最大可達到64像素見方,以實現對超高分辨率的最佳支持。爲了適應包傳輸網絡,有些標準允許一副圖像被劃分成多個Slice,每個Slice由圖像中若干連續的基本編碼單位組成。通過引入Slice,視頻數據包的大小得到了控制。此外,由於Slice之間一般不允許存在編解碼的依賴關係,即使發生丟包也不會出現錯誤蔓延。

由於混合編碼方案使用了基於時間軸的預測技術,在解碼過程中,圖像數據之間往往是存在依賴關係的,跟據依賴關係可以將圖像分爲三種類型:完全不依賴於其他圖像的圖像稱爲關鍵幀,完全不爲其它圖像依賴的稱爲可丟棄幀,其餘圖像屬於普通的參考幀,除了可丟棄幀之外,關鍵幀或參考幀的缺失或錯誤都會導致依賴於該圖像的其他圖像發生錯誤,且這種錯誤會隨着依賴關係蔓延,直至找到下一個關鍵幀結束當前的依賴關係,如下圖所示。對於的多媒體應用來說,事先獲取圖像的類型信息是非常有意義的,某些特定的操作需要這些信息,如隨機訪問和丟幀——前者要求儘快找到一個關鍵幀,而後者可以在需要的時候提高處理速度,但需要識別一副圖像是不是可丟棄。類型信息通常會包含在圖像頭中,有時也會作爲元數據附加到原始流之外。

在原始流中,圖像的順序與顯示順序不一定相同,這是由於編碼器在編碼過程中可能會對圖像順序進行了重排,因此,解碼器需要負責恢復圖像的顯示順序。

3.1.2 13818-1傳輸流

13818-1主要用於解決數字廣播系統中音頻、視頻及數據信息的複用問題,實際上就是給出一種同時傳輸音頻、視頻及數據的有效途徑。如前所述,在模擬時代,複用的問題可以通過頻分來解決,比如模擬電視通過將音頻數據調製到視頻信息帶寬之外來實現圖像和聲音的的同時傳輸。針對數字化的信息,13818-1定義了一個基於數據包的時分複用系統。這是一個面向比特流的傳輸系統,定義了傳輸流和節目流兩種不同形式的比特流:其中,傳輸流由固定長度的TS包組成,主要應用於數字廣播;節目流則以數據組爲單位,應用於數字存儲系統,數據組長度是可變的,通常也比TS包要大得多。

13818-1系統還必須保證在數據接收端重建音視頻及數據信息的同步,這要求在傳輸的數據中插入足夠多的時間信息。此外,多組使用不同時間基準的音視頻信息也可以按照13818-1規定的方式同時傳輸,這使得同時傳輸多個多媒體業務節目成爲可能,不過,只有傳輸流支持這種方式。

傳輸流的組成:

3.1.2.1 TS包

標準的TS包包含188個字節,除去4個字節的頭信息,還能傳輸184個字節的數據。頭信息重,13位的PID值是最重要的部分,那是一個類似子信道號的值,標識着TS包的數據“身份”。PID值爲0x1FFF的TS包爲空包,插入空包到傳輸流中只是爲了調整複用結構。payload_unit_start_indicator是頭信息中另一個比較重要的位段,對於以PES數據爲載荷的TS包,該位設1表示這個TS包中傳輸的是PES包的起首部分;對於以PSI數據爲載荷的TS包,該位設1表示這個TS包中傳輸的是PSI段的起首部分,而TS載荷的第一個字節是表示PSI數據在載荷中的位置的pointer字段。此外,transport_error_indicator字段表示包內的數據是否有比特錯誤;transport_scrambling_control表示包內的數據是否加密。

transport_packet(){
	sync_byte:8
	transport_error_indicator:1
	payload_unit_start_indicator:1
	transport_priority:1
	PID:13
	transport_scrambling_control:2
	adaptation_field_control:2
	continuity_counter:4
	if(adaptation_field_control=='10'  || adaptation_field_control=='11'){
		adaptation_field()
	}
	if(adaptation_field_control=='01' || adaptation_field_control=='11') {
		for (i=0;i<N;i++){
			data_byte
		}
	}
}

除了實際的PES或PSI載荷之外,跟在TS包頭後面的還可能是adaption域,這由TS頭中的另一個2比特的字段adaptation_field_control標誌決定。 adaption域包含的內容有:

  • discontinuity_indicator用於指示系統時鐘的不連續事件
  • random_access_indicator用於指示隨機訪問點
  • elementary_stream_priority_indicator用於指示ES數據優先度
  • PCR字段包含系統時鐘的採樣值,以27MHz時鐘週期爲單位,在PID爲特定值的TS包中傳輸
  • OPCR字段包含原始系統時鐘的採樣值,在PID爲特定值的TS包中傳輸。
  • splice_countdown字段用於指示音視頻載荷的銜接點
  • private_data包含13818-1規定外的數據。
3.1.2.2 PES包

在13818系統中,通過爲每一個TS包指定特定的PID可以實現多路數據的複用傳輸,PID不同的TS包可能承載着不同類型的數據,來自不同的編碼器或數據發生單元,需要接收端送至不同的接收處理單元。

PES包是由TS包承載傳輸的標準載荷之一,其內容包括MPEG定義的音、視頻信息(11172/13818/14496)、ECM/EMM信息、DSMCC信息以及各種自定義數據等。在傳輸之前,PES包必須被分割並分配到若干(至少一個)TS包的數據載荷部分。

一個PES包序列形成一路13818系統的數據流,其承載的數據被稱作原始流數據,它們可能是某個視頻編碼器的輸出、某個音頻編碼器的輸出,某個數據發生器的輸出或者某種控制信息,爲了使接收端能夠無縫地獲取到這些數據並將其送至特定的處理單元如視頻解碼器、音頻解碼器、數據處理單元或控制單元,傳輸這一路PES數據的TS包必須使用相同的PID(即佔用同一個子信道),並且保證在傳輸過程中不會發生亂序。

對於所有的PES包,首部有三個字段是必需的: packet_start_code_prefix(0x000001) stream_id(一個字節的流標誌字段,指示PES的載荷類型) PES_packet_length(兩個字節的長度字段),這個數值可以設爲零。

對於載荷類型除program_stream_map、padding_stream、private_stream_2、ECM、EMM、program_stream_directory、DSMCC_stream及ITU-T Rec. H.222.1 type E_stream外的PES包,13818-1還規定了更多的標準字段作爲補充性的首部信息:

  • PES_scrambling_control指示PES載荷是否被加擾;
  • PES_priority指示PES載荷的優先級;
  • copyright指示PES載荷是否存在版權保護;
  • original_or_copy指示PES載荷屬於原始信息還是拷貝信息;
  • PTS和DTS是以90kHz時鐘週期爲單位的時間標籤,分別表示顯示時間和解碼時間;
  • ESCR字段包含ES系統時鐘的採樣值,以27MHz時鐘的週期爲單位
  • ES_rate指示ES流速率。

3.1.2.3 輔助信息

承載輔助信息的數據在13818-1系統中被稱作表(table),每一種表由一個8比特的table id標識。其中,預定義的表有三種:節目關聯表(PAT)、條件接收表(CAT)和節目映射表(PMT),它們佔用的table id分別爲0、1和2,此外,0x03~0x3f範圍內的table id爲13818-1保留,0x40~0xFE範圍內的table id可用作私有擴展(DVB、ATSC等)。

在13818-1系統中,這些表需要按照規定的時間間隔重複傳輸,以使得接收端在任意的時間點都可以儘快地拿到所需要的全部輔助信息。每一種表會佔用一個PID,表中的數據以section的方式來組織,每個section的長度不超過4096字節,如果表的長度大於4096字節,則被分爲多個section來傳輸。 section的組織方式如下:

  • 方式一:
section() {		
	table_id		8	
	'1'			1	
	private_indicator	1	
	reserved		2	
	section_length		12	
	table_id_extension	16
	reserved		2
	version_number		5
	current_next_indicator	1
	section_number		8
	last_section_number	8
	for (i=0;i<section_length-9;i++) {
		data_byte
	}
	CRC_32			32
}
  • 方式二:
section() {		
	table_id		8	
	'0'			1	
	private_indicator	1	
	reserved		2	
	section_length		12
	for (i=0;i<N;i++) {
		data_byte
	}
	CRC_32			32
}

其中:

  • table_id標識表的類型。
  • private_indicator表示該表是否爲私有信息。
  • section_length爲緊隨該域的所有數據的長度(含4字節的CRC校驗)。
  • table_id_extension是對table id的一個16比特的擴充,對於不同的表有不同的含義。
  • version_number是個5比特的版本號。在重複輪播的過程中,表的內容也會發生變化,每次發生變化之後,要求這個版本號加一。
  • current_next_indicator是一個標誌位,標誌該表的內容即可生效還是在不久的將來生效,若該位爲0,則表示這個表是使用與將來的,起到一個通知接收端表內容即將發生變化的作用。
  • 如果一個表由多個section傳送,則第一個section的序號爲0,section_number表示當前section的序號,last_section_number表示最後一個section的序號,也就是傳送這個表的section的總數減去1。

最後,每一個section的數據會被分配到對應某一個PID的TS包載荷中。

2.1.2.4 複用的實現和PSI表

13818-1系統通過將不同的數據分配到PID不同的TS包內傳輸實現複用,而且,通過節目專用信息(PSI)表,該系統還可以實現多路多媒體業務節目的同時傳輸。

13818-1內定義的節目專用信息表有四種,分別爲節目關聯表(PAT),節目映射表(PMT),網絡信息表(NIT)和條件訪問表(CAT)。

PAT給出了一個傳輸流中各路多媒體業務節目的信息,其table id爲0,在pid爲0的TS包中傳輸,使用如下的語法結構:

program_association_section() {		
	table_id			8	
	section_syntax_indicator	1
	'0'				1
	reserved			2
	section_length			12
	transport_stream_id		16
	reserved			2
	version_number			5
	current_next_indicator		1
	section_number			8
	last_section_number		8
	for (i=0; i<N;i++) {		
		program_number		16
		reserved		3	
		if(program_number == '0') {		
			network_PID	13	
		}		
		else {		
			program_map_PID	13	
		}		
	}		
	CRC_32	32	
}  		

除去標準的section字段之外,需要關注的是:

  • transport_stream_id給出該PAT所屬的傳輸流的id,以與網絡中的其他傳輸流區分開來。
  • program_number是本傳輸流內的某路節目的編號。
  • program_map_PID是用於傳輸PMT的pid。

其中,N循環中包含了多組program_number和program_map_PID的結對,描述了用以傳輸各路節目的PMT section所使用的pid。(program_number爲0除外,它對應的program_map_PID爲傳輸NIT section所使用的pid)

PMT給出了某路多媒體業務節目的業務信息,即該多媒體節目中包含哪些媒體,分別爲何種類型,用於傳輸各個媒體數據的pid爲多少等,其table id爲2,傳輸使用的pid在PAT中指定,section格式如下:

TS_program_map_section() {	
	table_id			8
	section_syntax_indicator	1
	'0'				1
	reserved			2
	section_length			12
	program_number			16
	reserved			2
	version_number			5
	current_next_indicator		1
	section_number			8
	last_section_number		8
	reserved			3
	PCR_PID				13
	reserved			4
	program_info_length		12
	for (i=0; i<N; i++) {	
		descriptor()	
	}	
	for (i=0;i<N1;i++) {	
		stream_type		8
		reserved		3
		elementary_PID		13
		reserved		4
		ES_info_length		12
		for (i=0; i<N2; i++) {	
			descriptor()	
		}	
	}	
	CRC_32				32
} 	

其中,program_number爲該PMT所描述的節目的編號,和PAT中的program_number相同。N1循環中給出節目中各個媒體的信息:

stream_type爲媒體類型,如0x01爲11172-1視頻格式、0x02爲13818-2視頻格式、0x03爲11172-1音頻格式、0x04爲13818-2音頻格式、0x0f爲13818-7音頻格式、0x10爲14496-2視頻格式、0x11爲14496-3音頻格式、0x1b爲14496-10視頻格式等。而elementary_PID爲傳輸該媒體數據使用的pid值。

3.1.2.5 時鐘信息

13818-1傳輸流中的每一路節目都有一個獨立的27MHz的時鐘,該時鐘的採樣值被封裝到TS包的adaption域內定期傳送給接收端(至少100毫秒一次),使接收端的時鐘得以保持和發送端的同步,這些採樣值被稱爲節目時鐘參考(PCR),是一個42比特的值,表示0.037微妙的時鐘嘀嗒,極限值大約是45個小時。對於特定的某一路節目,用以傳輸PCR的pid是固定的,需要在該節目的PMT中指明。當PCR發生突變的時候,發送端需要預先通知接收端,這同樣要使用到adaption域,時鐘突變發生之前,傳輸PCR的adaption域的discontinuity_indicator需要被置1。

音視頻等媒體的時間標籤封裝在PES頭中,有PTS和DTS兩種,分別表示媒體的播放時間和解碼時間,它們都是以27M的系統時鐘爲基準的,但時間的顆粒度更粗一些,爲90KHz。PTS和DTS的位長都是33比特,表示11微妙的時鐘嘀嗒,極限值大約是26個小時。

3.1.2.6 同步

13818-1系統是強同步的,這意味着,接收端不僅要保證特定節目內的每一種媒體(音頻、視頻及字幕等)按照給定的速率播放、媒體間的同步關係得以保持(脣音同步、字幕同步等),還要保證與發送端使用一致的時鐘,也就是說所有接收端在播放同一個廣播節目時的表現必須完全相同。

因此,接收端必須根據傳輸流中的PCR值內建一個本地時鐘,而且,還必須依據收到的PCR值對這個本地時鐘不斷校正,以彌補編碼和傳輸過程中引入的PCR抖動。而所有媒體的播放控制都依賴於這個本地時鐘,目標就是令每個媒體包的播放時刻與它的時間戳完全一致。

3.2 容器

在數字媒體領域,容器專指數字媒體數據的封裝格式。音頻、視頻及其他數據藉助於某個特定的容器可以被組織、複用到同一個文件之中,從而滿足某些特定的操作要求(主要是針對播放器),如播放、暫停、快進、後退、跳轉等。 容器不涉及多媒體數據的具體編碼方式。

3.2.1 AVI

AVI是微軟使用的一種多媒體文件格式,由於迄今爲止Windows一直是PC的主流操作系統,AVI也成爲PC中最流行的多媒體文件格式。不過這種格式並非微軟的原創,它最早是由電子藝界提出的,其特徵是使用一種稱爲chunk的數據塊來存儲多媒體數據及其附加信息。每個chunk只有八字節的頭,前四字節是四個ASCII碼,作爲該chunk的標識;後四字節爲一個整數,表示緊跟在頭信息後面的數據的長度,結構非常簡單。此外,還有分別以”RIFF”和”LIST”爲標識的兩種複合chunk,它們的數據內容爲多個chunk組成的序列,從而使數據的層次化得以實現。

3.2.1.1 結構

一個avi文件在結構上由一個RIFF複合chunk組成,因此,首四個字節 “52494646”是RIFF的ASCII碼,接下來的四個字節表示該複合chunk的長度,亦即該avi文件的長度減8,至於數據部分,則首先是四個字節“41564920”,即ASCII的“AVI ”,表示這個複合chunk的具體名稱,然後纔是組成該avi的各個chunk。通常,一個avi文件包括一個名爲“hdrl”的LIST,存放所有頭相關信息;一個名爲“movi”的LIST,存放所有媒體數據;然後是一個標識爲“idx1”的chunk,存放相關索引信息。索引信息描述了各個數據塊在 LIST中的位置,有助於提高SEEK操作的速度。

需要注意的是,chunk中的數據是要求雙字節對齊的,如果某個chunk的長度是奇數,那麼其後要填一個零。

AVI RIFF File Reference from the Microsoft site:

“The data is always padded to nearest WORD boundary. ckSize gives the size of the valid data in the chunk; it does not include the padding, the size of ckID, or the size of ckSize.”

圖3. AVI文件的結構:

一個實例:

RIFF[AVI ]+60337434B
    LIST[hdrl]+8830B
        avih+56B
        {FPS:1000000/41667, 0bps, 0 Byte Aligned, /HASINDEX/INTERLEAVED 2758 frames, Inital 0, 2 streams, Buffer:0 Bytes, 1280x720}
        LIST[strl]+4244B
            strh+56B
            {[vids], [divx], Initial 0, 24/1, 0+2758, Buffer:368324 Bytes, quality:0x2710, Size of Sample:0, }
            strf+40B
            {1280x720, 24 Bits, DX50}
            JUNK+4120B
        LIST[strl]+4234B
            strh+56B
            {[auds], [], Initial 1, 24000/1, 0+2753491, Buffer:12000 Bytes, quality:0x0, Size of Sample:1, }
            strf+30B
            {id=0x55, 2 ch, Sample rate:44100, Bit rate:192000, 1 block align, 0 bits, }
            JUNK+4120B
        LIST[odml]+260B
            dmlh+248B
        LIST[INFO]+56B
            ISFT+44B
            {VirtualDubMod 1.5.10.1 (build 2439/release)}
        JUNK+1318B
    LIST[movi]+60239170B
        01wb+12000B
        00dc+70699B
        01wb+1000B
        00dc+465B
        01wb+1000B
        00dc+466B
        01wb+1000B
        ......
    idx1+88016B
       {fourcc=01wb, flag=0x10, pos=4, len=12000}
       {fourcc=00dc, flag=0x10, pos=12012, len=70699}
       {fourcc=01wb, flag=0x10, pos=82720, len=1000}
       {fourcc=00dc, flag=0x0, pos=83728, len=465}
       {fourcc=01wb, flag=0x10, pos=84202, len=1000}
       {fourcc=00dc, flag=0x0, pos=85210, len=466}
       {fourcc=01wb, flag=0x10, pos=85684, len=1000}
       ......

以上結構可以通過一系列簡單的C函數來理解,主要涉及到五個Microsoft數據結構:

typedef struct _avimainheader {
    FOURCC fcc;                  // 'avih'
    DWORD  cb;                   // size of the header, initial 8 bytes excluded 
    DWORD  dwMicroSecPerFrame;   // frame period in microsecond
    DWORD  dwMaxBytesPerSec;     // maximum bitrate
    DWORD  dwPaddingGranularity; // alignment for data, in bytes  
    DWORD  dwFlags;
    DWORD  dwTotalFrames;        // total number of frames of data in the file
    DWORD  dwInitialFrames;      // initial frame for interleaved files. Noninterleaved files should specify zero
    DWORD  dwStreams;            // the number of streams in the file
    DWORD  dwSuggestedBufferSize;// suggested buffer size for reading the file
    DWORD  dwWidth;              // width of the AVI file in pixels
    DWORD  dwHeight;             // height of the AVI file in pixels
    DWORD  dwReserved[4];        // reserved. Set this array to zero. 
} AVIMAINHEADER;
 
typedef struct _avistreamheader {
     FOURCC fcc;                 // 'strh'
     DWORD  cb;                  // size of the header, initial 8 bytes excluded 
     FOURCC fccType;             // data type of the stream
     FOURCC fccHandler;          // data handler, preferred codec for audio and video
     DWORD  dwFlags;
     WORD   wPriority;           // highest priority might be the default stream
     WORD   wLanguage;           // Language tag
     DWORD  dwInitialFrames;
     DWORD  dwScale;             // dividing dwRate by dwScale gives the number of samples per second
     DWORD  dwRate;
     DWORD  dwStart;             // starting time for this stream
     DWORD  dwLength;            // length of this stream 
     DWORD  dwSuggestedBufferSize;   // how large a buffer should be used to read this stream
     DWORD  dwQuality;           // an indicator of the quality of the data
     DWORD  dwSampleSize;        // the size of a single sample of data, 0 for video
     struct {
         short int left;
         short int top;
         short int right;
         short int bottom;
     }  rcFrame;                // destination rectangle for display
} AVISTREAMHEADER;
 
typedef struct { 
  WORD  wFormatTag; 
  WORD  nChannels; 
  DWORD nSamplesPerSec; 
  DWORD nAvgBytesPerSec; 
  WORD  nBlockAlign; 
  WORD  wBitsPerSample; 
  WORD  cbSize; 
} WAVEFORMATEX; 
 
typedef struct tagBITMAPINFO {
  BITMAPINFOHEADER bmiHeader;
  RGBQUAD          bmiColors[1];
}BITMAPINFO, *PBITMAPINFO;
 
typedef struct tagBITMAPINFOHEADER {
  DWORD biSize;          // The number of bytes required by the structure
  LONG  biWidth;         // The width of the bitmap, in pixels
  LONG  biHeight;        // The height of the bitmap, in pixels
  WORD  biPlanes;        // must be set to 1
  WORD  biBitCount;      //  The number of bits-per-pixel
  DWORD biCompression;   // The type of compression, codec indicator 
  DWORD biSizeImage;     // The size of image buffer
  LONG  biXPelsPerMeter; // The horizontal resolution, in pixels-per-meter
  LONG  biYPelsPerMeter; // The virtical resolution, in pixels-per-meter
  DWORD biClrUsed;       // The number of color indexes in the color table that are actually used by the bitmap
  DWORD biClrImportant;  // The number of color indexes that are required for displaying the bitmap
}BITMAPINFOHEADER, *PBITMAPINFOHEADER;

AVI文件的頭信息存放在以”avih”爲標識的chunk中,其數據格式由AVIMAINHEADER結構描述,但實際上裏面的多數信息都屬於冗餘信息,因爲諸如視頻幀率、比特率、視頻的寬度和高度等參數都會在其後的媒體流頭信息和媒體流格式信息中給出,而且,許多編碼器還會將這些信息硬編碼到媒體流內部,因此,一般不必它們進行處理。參考以下來自FFMPEG的一個處理avi的代碼片斷:

        case MKTAG('a', 'v', 'i', 'h'):
            /* AVI header */
            /* using frame_period is bad idea */
            frame_period = get_le32(pb);
            bit_rate = get_le32(pb) * 8;
            get_le32(pb);
            avi->non_interleaved |= get_le32(pb) & AVIF_MUSTUSEINDEX;
 
            url_fskip(pb, 2 * 4);
            get_le32(pb);
            get_le32(pb);
            avih_width=get_le32(pb);
            avih_height=get_le32(pb);
 
            url_fskip(pb, size - 10 * 4);
            break;

接下來是若干名爲”strl”的LIST Chunk,用以傳輸媒體流的頭信息,每一個媒體流對應一個LIST,譬如:對於一路音頻加一路視頻的AVI文件,則分別有一個描述音頻流的”strl” LIST和一個描述視頻流的”strl” LIST。整個LIST由兩個chunk組成:第一個chunk的ascii標識是”strh”,其數據格式由AVISTREAMHEADER描述,給出了若干通用的媒體信息;第二個chunk的ascii標識是”strf”,其數據長度和數據格式因媒體的不同而不同,對於視頻流,它的數據是一個BITMAPINFO的結構,而對於音頻,則是一個WAVEFORMATEX的結構。

從AVI頭和”strl” LIST可以獲得如下信息:

  • 媒體流的數目

AVIMAINHEADER的dwStreams成員會給出文件中包含的媒體流數目,不過,一般會依據文件中包含的”strl” LIST的實際個數來判斷。

  • 媒體編碼格式

AVISTREAMHEADER的fccHandler會給出一個對應的媒體流所使用的codec的ascii碼標識,此外,在描述視頻流的BITMAPINFO結構中有一個biCompression成員,在描述音頻流的WAVEFORMATEX結構中有一個wFormatTag,它們也會出對應音視頻流的codec信息,其中大部分codec的定義來自RFC2361

  • 文件長度

AVISTREAMHEADER的dwTotalFrames給出了媒體流的總幀數。

  • Buffer的大小

AVIMAINHEADER中有dwSuggestedBufferSize,AVISTREAMHEADER還有dwSuggestedBufferSize。

  • Sample Size

AVISTREAMHEADER中有dwSampleSize字段,微軟在MSDN中是如是解釋的:

該字段給出一個數據採樣的大小(字節)。若各數據採樣大小不同則設該字段爲0。該字段不爲0時,多個數據採樣可以被組合到一個chunk中。該字段爲0(比如視頻)時,每個數據採樣必須佔有一個獨立的chunk。對於視頻流,這個值通常被設爲0(雖然各視頻幀大小相同時也可設爲非零)。對於音頻流,這個數值應該同描述音頻的WAVEFORMATEX結構中nBlockAlign成員相等。(Specifies the size of a single sample of data. This is set to zero if the samples can vary in size. If this number is nonzero, then multiple samples of data can be grouped into a single chunk within the file. If it is zero, each sample of data (such as a video frame) must be in a separate chunk. For video streams, this number is typically zero, although it can be nonzero if all video frames are the same size. For audio streams, this number should be the same as the nBlockAlign member of the WAVEFORMATEX structure describing the audio.)

而實際應用中有如下幾種情況:對於視頻來說,一個數據採樣就是一個視頻幀,除非是非壓縮數據,不然很難令每個視頻幀的字節數相等,因此,視頻數據的Sample Size往往是0,每個視頻幀佔用一個chunk。對於音頻來說,情況略複雜:

  1. 0值,表示變比特率,一個數據採樣表示一個音頻幀,和視頻流一樣,每個音頻幀也佔用一個chunk,此時nBlockAlign給出音頻幀的採樣點個數,nSamplesPerSec/nBlockAlign可獲取幀率。
  2. 1值,此時dwSampleSize是一個沒有意義的值,每個數據Chunck存放一個音頻幀,固定比特率(CBR)的音頻壓縮數據往往這麼做。
  3. 以採樣點記的幀長度,通常只適用於固定比特率的音頻。在這種情況下,dwSampleSize等於nBlockAlign,表示一個音頻幀的長度,但這個長度指的不是字節數,而是採樣點數,一個音頻幀的字節數爲:(dwSampleSize*bitrate)/(sample_rate*8)。比如,採樣率48000、比特率63952、幀長1152的mp3格式,每幀數據的字節數爲(1152*63952)/(48000*8))=192。在這種情況下,一個Chunck中可能會存放多個音頻幀,則解包時需要計算每一幀數據的字節數。
  4. 以字節記的採樣點長度,音頻數據。往往針對PCM。有這種情況?
  • 時間信息

AVISTREAMHEADER中有兩個成員dwScale和dwRate,以dwRate/dwScale的形式給出對應媒體流每秒鐘的樣本個數。對於視頻數據,dwRate/dwScale即是幀率,如果不考慮B幀的影響,第n幀的時間信息即爲n*dwScale/dwRate;對於音頻數據,其意義因dwSampleSize的不同而不同:

  1. 對應dwSampleSize情況1,dwRate/dwScale給出音頻的幀率,且每個chunk包含一個音頻幀,時間信息的計算方法與視頻流相同。
  2. 對應dwSampleSize情況2,dwRate/dwScale給出的是音頻數據每秒鐘的字節數,即比特率除以8,可得到第n個字節的時間信息爲n*dwScale/dwRate。
  3. 對應dwSampleSize情況3,dwRate/dwScale給出音頻的幀率,但需要事先計算出每個音頻幀的字節數,然後才能確認每幀的時間信息。
  4. 對應dwSampleSize情況4,dwRate/dwScale給出音頻的採樣率,第n個採樣點的時間信息爲n*dwScale/dwRate。有這種情況???
  • 視頻信息

幀率和分辨率是兩個對於播放器來說極其重要的數據,它們給出了視頻數據的基本時間信息和空間信息。AVIMAINHEADER有描述幀時長的dwMicroSecPerFrame,這個值並非很可靠,更多情況下會參考6)中的提到的dwScale和dwRate成員,但事實上,許多視頻編碼標準會將幀率的信息直接編碼到視頻流中,相對於前二者,這個幀率纔是最可靠的。與幀率類似,AVIMAINHEADER、BITMAPINFO以及原始視頻流中都會有視頻高度和寬度的信息,仍舊是以原始視頻流中的信息最爲可靠。

  • 音頻信息

音頻流的信息定義在一個WAVEFORMATEX結構中。起首的兩字節字段wFormatTag定義了音頻的格式,如:

  • 0x0001:PCM格式。
  • 0x0003:IEEE Float格式
  • 0x0006:G.711 A律格式
  • 0x0007:G.711 µ律格式

而通道數、採樣率、比特率、塊長度和採樣位長分別由nChannels、nSamplesPerSec、nAvgBytesPerSec/8、nBlockAlign和wBitsPerSample給出。最後2字節的cbSize則是接下來的擴展長度,可以爲0。

3.2.1.2 媒體數據

媒體數據以Chunk的形式存放在”movi” 每個Chunk的FOURCC對應媒體流的id,其中前兩個字節表示媒體的類型,後兩個字節表示媒體流的序號,通常,”wb”表示音頻,”dc”表示壓縮視頻,”db”表示非壓縮視頻,“sb”表示字幕。一般情況下,每個Chunk存放一個視頻幀或音頻幀。

3.2.1.3 Meta Data

AVI允許使用一個名爲“INFO”的LIST存放一些metadata,以下是FFMPEG中給出的AVI metadata的定義,其中較爲常見的是ISFT,描述生成AVI文件的應用程序:

const AVMetadataConv ff_avi_metadata_conv[] = {
    { "IART", "artist"    },
    { "ICMT", "comment"   },
    { "ICOP", "copyright" },
    { "ICRD", "date"      },
    { "IGNR", "genre"     },
    { "ILNG", "language"  },
    { "INAM", "title"     },
    { "IPRD", "album"     },
    { "IPRT", "track"     },
    { "ISFT", "encoder"   },
    { "ITCH", "encoded_by"},
    { "strn", "title"     },
    { 0 },
};
3.2.1.4 索引和隨機訪問

隨機訪問是播放器必不可少的一項功能,它允許用戶可以直接訪問某個時間點上的媒體內容。但是,文件系統中往往以字節偏移爲單位進行隨機訪問,因此,一種多媒體容器必須提供一種字節偏移和時間的對應表,即索引,它的作用和我們平時讀書的目錄是相同的,假如沒有目錄,我們不得不從頭一頁一頁地翻書,直到找到需要的內容。 AVI的索引數據在文件的末尾,是一個名爲”idx1”的數據塊,包含了”movi” List中所有數據塊的索引信息。每個索引項的結構如下:

typedef struct {
    DWORD  ckid;
    DWORD  dwFlags;
    DWORD  dwChunkOffset;
    DWORD  dwChunkLength;
} AVIINDEXENTRY;

ckid是該索引對應的數據塊的id,如”00dc”,”01wb”等; dwFlags給出幾個標誌,主要是用於表明對應數據塊是否爲關鍵幀(0x10表示關鍵幀);接下來兩個32位值分別表示對應數據塊在文件中相對於movi List的字節偏移及數據塊的長度。 由上可見,AVI的索引項中沒有時間信息,第n個索引項對應的即是第n個數據chunk的信息。 一個示例:

example.avi
  video stream:   00dc, 29.97 fps;
  audio stream 1: 01wb, 448000 bps;
  audio stream 2: 02wb, 192000 bps
  
original index information: 
01wb 0x10 4      28000
02wb 0x10 28012  12000
00dc 0x10 40020  3356
01wb 0x10 43384  1869
02wb 0x10 45262  801
00dc 0x00 46072  159  
01wb 0x10 46240  1869
02wb 0x10 48118  801
00dc 0x00 48928  159
......

parsed index information:
video stream:
t = 0        s; offset = 40020 B; length = 3356 B; [KEY]
t = 100/2997 s; offset = 46072 B; length = 159 B; 
t = 200/2997 s; offset = 48929 B; legnth = 159 B;
...
audio stream 1:
t = 0           s; offset = 4     B; length = 28000 B;
t = 28000/56000 s; offset = 43384 B; length = 1869 B;
t = 29869/56000 s; offset = 46240 B; length = 1869 B;
...
audio stream 2:
t = 0           s; offset = 28012 B; length = 12000 B;
t = 12000/24000 s; offset = 45262 B; length = 801 B;
t = 12801/56000 s; offset = 48118 B; length = 801 B;

索引數據的缺失或破損嚴重影響AVI文件的隨機訪問,即使我們可以粗略地估計一個位置,但由於讀到的AVI數據Chunk中不包含時間標籤,也會導致音視頻之間失去同步關係。畢竟AVI是一種爲本地播放文件而設計的格式,它不會爲每個數據包提供顯式的時間信息,其時間關係完全隱含在數據的存儲組織關係內。

3.2.1.5 交織

顧名思義,AVI格式的一個初衷即是實現音頻和視頻數據的交織存儲,這有助於播放器在順序讀取數據的情況下實現音視頻的同步,因爲某些設備如硬盤、光驅等並不適合頻繁的隨機訪問,對於播放器來說,只需要關注合適的緩衝策略和同步策略即可。

3.2.1.6 打包和解包

視頻數據和變比特率音頻數據的打包通常以幀爲單位,一個Chunk存放一幀壓縮圖像或聲音;

對於固定比特率的音頻數據,允許多個壓縮的幀存放在一個chunk中,但要求首部信息給出幀長度(通常原始音頻單幀採樣點個數的形式給出,比如對於mp3,這個數值一般是1152),否則無法將Chunk中的多個包分割開來。

3.2.1.7 一些結論

AVI文件語法結構簡單,易於解析,媒體數據採樣存儲在獨立的chunk中,有簡單的同步標記和長度信息,即使文件的索引部分和頭部分發生損壞,媒體數據也依然能夠被讀取。然而,AVI的缺陷也確實不少,一個致命的缺點是缺少時間標籤,只能根據全局幀率或比特率累積推測時間信息,對於變幀率視頻和變比特率音頻的支持有限;其次,每個流沒有獨立的索引信息,索引信息的位置也沒有在頭部給出;此外,AVI的可擴展性也不好,不支持超級大文件,不支持媒體數據打包時的分割和分組,不支持數字版權管理(Divx公司通過定義新的chunk實現了其專有的DRM策略),也無法滿足日益迫切的流媒體需求。這些缺陷在之後出現的類似媒體格式(包括微軟推出的ASF和Real推出的realmedia等)中得到了修正。

目前AVI主要用於本地文件回放,來源包括一些不合法的DVD轉碼拷貝軟件以及Divx公司的軟件。

3.2.1.8 ODML擴展

ODML擴展修正了許多AVI固有的缺陷,如不支持大文件(> 2G Bytes)、多個媒體流共用一個索引數據塊等。

ODML擴展的AVI文件提供一種方法允許文件中含有多個RIFF,以突破2G長度限制。第一個RIFF爲主RIFF(RIFF 'AVI '),內含”hdrl”信息,而其他的RIFF爲擴展RIFF(RIFF ‘AVIX’),僅包含”movi” LIST。主RIFF的”hdrl”中會包含一個”odml” LIST,其中的”dmlh” Chunk包含有整個AVI文件的長度。

ODML擴展的AVI文件還提供了新的索引結構,其一、索引信息位於”strl”中,以”indx”爲FOURCC,各個媒體流有自己的索引數組;其二、支持分層索引,即”indx”中的 索引項可以是一系列指向二級索引表的指針,而FOURCC爲”ix00”形式的Chunk作爲二級索引表散佈”movi”中。這裏是一個解析ODML索引的示範程序。

3.2.1.9 DivX

DivX文件格式是Divx公司在AVI的基礎上開發的一種多媒體容器,主要是提供了一些類似與DVD的功能,如交互式菜單、多字幕、多音軌、章節支持、數字版權保護等。一般來說,divx文件是與AVI保持兼容的,一個支持AVI的播放器可能不會支持DivX的某些擴展功能,但至少可以播放其中的音視頻。

3.2.2 ISO標準及其衍生

3.2.2.1 QTFF

QTFF(Quick Time File Format)是蘋果公司推出的多媒體文件格式,第一個版本於1991年隨着Quick Time多媒體框架一起問世,通常以“MOV”爲擴展名。

QTFF的基本組成單位是Atom,最基本的Atom是由長度、類型和數據三部分組成的,其中長度和類型分別爲4字節長,長度部分能夠支持擴展爲8個字節。某些複雜一點的Atom規定在長度和類型之後增加1個字節的版本號和3個字節的標記位,以支持更多擴展。類型字段則往往是四個ASIC字符,這在多媒體文件格式中及其常見。一個Atom的數據允許包含子Atom,因此,在理論上Atom是可以自由嵌套的。

3.2.2.2 ISO媒體文件標準

2001年,ISO在QTFF的基礎上制定了一個多媒體文件標準(ISO/IEC 14496-12),由於具備非常好的功能性和擴展性,該標準逐漸得到了業界的認可。在網站http://www.mp4ra.org/atoms.html列出了目前經過認證的ISO文件擴展,其中包含了3GPP、MP4以及QTFF格式。

ISO媒體文件將QTFF中的Atom重定義爲盒子(Box),構造方式並無變化,而且基本的盒子的定義也與QTFF保持一致。雖然QTFF的出現先於ISO,但仍可以將QTFF看作是ISO標準的某種擴展。

圖4. 一個典型的ISO格式文件

這裏有一段C++代碼,用於實現ISO媒體文件的解析。

盒子的定義和包含關係大致如下:

  • ftyp:文件類型,主要包括當前格式的版本號,兼容性等信息。
  • mdat:數據塊,以Chunk爲單位交錯存放的媒體數據。
  • moov:影片信息,含有子字段。
    • mvhd:影片信息頭,含有影片的時間粒度及此粒度下的影片時長等字段。
    • trak:媒體軌道信息,含有子字段。
      • tkhd:媒體軌道信息頭,含有媒體軌道的標識號、以影片時間粒度度量的時長等信息。
      • mdia:媒體信息,含有子字段。
        • mdhd:媒體頭,該媒體的時間粒度及此粒度下的時長。
        • hdlr:媒體類型,含有一個FourCC標識的媒體類型,如vide、soun、subt等。
        • minf:媒體詳細信息,含有子字段。
          • vmhd/smhd/……:視頻、音頻及其他媒體的基本描述
          • dinf:數據信息。
          • stbl:樣本表,內含媒體信息的詳細信息。

其中,moov→trak→mdia→minf中的stbl是一個比較重要的box,其中包含的stsd box內有解碼器需要的媒體描述信息;stss內有關鍵幀信息;stts、stco、stsc、stsz用於構建索引,其中stts給出每個數據幀的時間信息、stco給出每個數據Chunk在文件中的偏移、stsc給出個各個數據Chunk中包含的數據幀、stsz給出各個數據幀的長度;所有媒體數據則統一存放在mdat box中,沒有同步字,沒有分隔符,只能根據索引進行訪問。mdat的位置比較靈活,可以位於moov之前,也可以位於moov之後,但必須和stbl中的信息保持一致。但是,如果mdat的位置在moov之前,通過流的方式播放文件會出現問題,因爲沒有辦法在一開始就獲得文件的媒體信息和索引。

Box的擴展通過uuid實現。用戶可以使用類型爲'uuid'的box,以16個特定的字節作爲標識,定義自己的數據格式。

目前,各種類ISO 14496-12格式如MOV、F4V、3GP等在數碼相機、互聯網視頻、移動視頻等領域應用相當廣泛,然而由於HTML5的問世,其主導地位受到基於MKV的WebM格式的威脅。

3.2.2.3 媒體信息的封裝

3.2.3 ASF

1995年起微軟着手開發新的媒體格式ASF,這是一種適合網絡傳輸的流媒體格式。相對於AVI而言,ASF引入了很多改進,包括:

  • 使用128比特的GUID代替四個字節的fourCC;
  • 使用64比特的長度域,支持超大文件;
  • 定義了三個基本的頂層對象:Header,Data和Index,這些對象可以獨立存儲、傳輸;
  • 數據打包允許分割(一個原始媒體數據分成多個包)和分組(多個原始媒體數據打入一個包);
  • 數據包的首部包含有時間信息。
  • 支持基於絕對時間、相對偏移的索引信息,支持多個媒體流的獨立索引。
3.2.3.1 結構

一個ASF文件由三種最基本的頂層對象組成:Header, Data和Index,亦即頭、數據和索引,其中,索引是非強制的。頭對象相當於AVI的”hdrl” List,內部可以包含多個子對象;數據對象相當於AVI的”movi” List,由多個封裝媒體數據的包組成;而索引則相當於”idx1” Chunk,給出各個媒體的數據在數據對象內的偏移與時間的對應關係。通常,一個典型的ASF文件的結構如下:

ASF Object Found with Size 144957266
      Header Object Found with Size 5326
          Codec List Object Found with Size 302
          File Properties Object Found with Size 104
          Header Extension Object Found with Size 4268
          Extended Content Description Object Found with Size 328
          Stream Properties Object Found with Size 122
          Stream Properties Object Found with Size 134
          Unknown Object Found with Size 38
      { 7 headers contained. }
      Data Object Found with Size 144948546
      { 8114 packets contained. }
      Index Object Found with Size 1934
      Simple Index Object Found with Size 1460

ASF不支持嵌套結構,頭對象是唯一可以包含子對象的對象,內含有各種與文件結構和媒體流相關的信息。

File Properties對象相當於AVI中的”avih” Chunk,給出了媒體文件的時長、數據包的總數、預緩衝時長、比特率以及一些標誌等信息。其中,如果預緩衝時長不爲零,則說明媒體文件時長以及數據包的時間戳已經加上了這個預緩衝時長。

Stream Properties對象相當於AVI中的”strl” List,基本上是將AVI中”strh”和”strh”中信息統一到一個數據結構中。其中,Stream Type以GUID的形式給出,Stream Number限定爲1~127。而與媒體類型相關的信息結構也因媒體類型的不同而不同,與AVI一樣,音頻信息使用WAVFORMATEX結構,而視頻信息使用BITMAPINFOHEADER結構。Stream Properties對象的個數取決於文件中包含的媒體流個數,每一個媒體流都需要一個Stream Properties對象(由於File Properties對象中並不象AVI文件的”avih”一樣包含媒體流個數,需要通過統計Stream Properties對象的個數來得到文件包含的媒體流個數)。

Header Extension對象允許通過嵌入子對象定義擴展的頭信息。 這裏有一個簡單的解析ASF文件的JAVA程序

3.2.3.2 打包和解包

ASF是一種流媒體格式,基本的媒體數據封裝單位爲包,封裝方式非常靈活:包長可以是固定的,也可以是可變的;包內可以含有一個載荷,也可含有多個載荷(最多64個);每個載荷可以是部分媒體數據幀,也可以是整個媒體數據幀,還可以是多個媒體數據幀。下圖給出各種封裝方式的示意:

當採用固定包長的方式封裝時,包頭中的Packet Length通常可以省略,實際的包長在文件頭對象中給出,如果採用可變包長的方式進行封裝,Packet Length字段必須出現在包頭中,可以是8位、16位或32位,最大的包長可以達到4GB。包頭中的時間戳表示該包的發送時間。

載荷緊跟包頭,由首部和數據組成,由於載荷中承載的不一定是完整的數據幀,其首部需要給出完整的數據幀的長度和當前載荷在數據幀中的偏移量,以便解包程序進行組幀。當一個包內含有多個載荷時,不同的載荷可以來自不同的媒體流,也就是說在一個包內就可以實現媒體的交錯。

在某些情況下,一個載荷中可以包含多個數據幀,但要求:

  1. 所有的數據幀來自同一個媒體流
  2. 所有的數據幀都是關鍵幀或都不是關鍵幀
  3. 長度在256個字節之內
  4. 幀率固定
3.2.3.3 索引

ASF Index對象提供索引信息,由若干索引塊組成,每個索引塊包含多個索引項,各索引項對應的的時間點的間隔給定,索引項內包含各個媒體流的數據在該時間點上對應的偏移,這個偏移需要加上索引塊中給出的一個基礎偏移,最終的結果是相對於數據對象中第一個數據包的偏移。

索引信息有三種:

  1. 指向最近的包含給定時間點上的媒體數據幀的數據包;
  2. 指向包含給定時間點上的媒體數據幀的起始部分的數據包;
  3. 指向包含距給定時間點上的媒體數據幀最近的關鍵幀的起始部分的數據包。
sending time:  |  1000  |  2000  |  3000  |  4000  |  5000 |  6000  |  7000  |
frame number:  |  1     |  1     |  2     |  2     |  3    |  3     |  3     |
Key:           |  Y     |  Y     |  N     |  N     |  N    |  N     |  N     |
               ^                                   ^                ^ 
time=7750      |                                   |                |
          index type #3                       index type #2  index type #1

3.2.4 RealMedia

這是RealNetworks開發的一種多媒體文件格式,早期只支持固定比特率(CBR),擴展名爲rm,屬於最早的互聯網流媒體格式之一,曾經紅極一時。然而,雖然CBR支持有限帶寬下的高效數據傳輸,卻無法保證視頻質量,這使得rm一度成爲低質視頻的代名詞,有時某些畫面幾乎是慘不忍睹。後來,RM逐漸退出了互聯網視頻舞臺,取而代之的是FLV和基於ISO格式的F4V,雖然後來RealNetworks在新的編碼器中增加了變比特率(VBR)的支持(以rmvb爲文件擴展名),仍舊無力迴天,目前,Real格式僅在中國地區比較流行。

3.2.4.1 文件結構

RealMedia文件由首部、數據和索引三大部分組成,數據組織方式與ASF非常相似。 

Chunk是基本的數據構成單位,每個Chunk起首爲一個32位的FourCC和一個32位的長度字段,數據緊隨其後。FourCC是四個ASIC字符,標識Chunk的類型,長度字段則標明淨數據的字節數。

首部包含四種Chunk:File Header, Properties, Media Properties和Content Description,FOURCC分別爲:“.RMF”,“PROP”,“MDPR”和“CONT”,詳細的數據結構如下所示:

RealMedia_File_Header
{
  UINT32    object_id;
  UINT32    size;
  UINT16    object_version;

  if ((object_version == 0) || (object_version == 1))
  {
    UINT32   file_version;
    UINT32   num_headers;
  }
}

在File Header中,num_headers給出接下來文件中Chunk的個數,這個數目不僅僅包含了各種首部Chunk,也包含了DATA Chunk和INDX Chunk(但對於多INDX Chunk,似乎只算做一個)。

Properties
{
  UINT32    object_id;
  UINT32    size;
  UINT16    object_version;

  if (object_version == 0)
  {
    UINT32   max_bit_rate;
    UINT32   avg_bit_rate;
    UINT32   max_packet_size;
    UINT32   avg_packet_size;
    UINT32   num_packets;
    UINT32   duration;
    UINT32   preroll;
    UINT32   index_offset;
    UINT32   data_offset;
    UINT16   num_streams;
    UINT16   flags;
  }
}

Properties結構給出了比特率、包長、數據包的總數、時長、索引塊的偏移、數據塊的偏移以及媒體流的數目。

Media_Properties
{
  UINT32     object_id;
  UINT32     size;
  UINT16     object_version;

  if (object_version == 0)
  {
    UINT16                      stream_number;
    UINT32                      max_bit_rate;
    UINT32                      avg_bit_rate;
    UINT32                      max_packet_size;
    UINT32                      avg_packet_size;
    UINT32                      start_time;
    UINT32                      preroll;
    UINT32                      duration;
    UINT8                       stream_name_size;
    UINT8[stream_name_size]     stream_name;
    UINT8                       mime_type_size;
    UINT8[mime_type_size]       mime_type;
    UINT32                      type_specific_len;
    UINT8[type_specific_len]    type_specific_data;
  }
}

Media Properties給出特定媒體流的比特率、包長、起始時間、時長、媒體名稱、MIME類型等信息。此外,這個結構的最後可以是一個依賴於具體流類型的黑盒結構,內含與媒體流的相關的特定信息,如編解碼參數等,從而提高這一容器的可擴展性,使其能夠支持各種不同的媒體流。

對於視頻流來說,主要的信息包括:

Field Name bits
長度 32
類型標記(VIDO) 32
子類型標記 32
寬度 16
高度 16
比特位數 16
填充寬度 16
填充高度 16
幀率 32
Content_Description
{
  UINT32     object_id;
  UINT32     size;
  UINT16      object_version;

  if (object_version == 0)
  {
    UINT16    title_len;
    UINT8[title_len]  title;
    UINT16    author_len;
    UINT8[author_len]  author;
    UINT16    copyright_len;
    UINT8[copyright_len]  copyright;
    UINT16    comment_len;
    UINT8[comment_len]  comment;
  }
}

Content Description中包含了一些簡單的影片說明信息(相當簡單)。

通常,數據部分緊接着首部,由一個或多個以”DATA”爲標識的Chunk組成,每個數據Chunk包含了一系列交織的媒體包以及一個指向下一個數據Chunk的指針:

Data_Chunk_Header
{
  UINT32     object_id;
  UINT32     size;
  UINT16      object_version;

  if (object_version == 0)
  {
    UINT32    num_packets; 
    UINT32    next_data_header;
  }
}

以上是Chunk的頭結構:num_packets給出了本Chunk內數據包的個數,next_data_header如果不爲0的話表示下一個數據Chunk相對於文件首的偏移量,不過Real的SDK文檔中說:“This field is not typically used”。

索引Chunk以”INDX”爲標識,每一個媒體流有自己對應的索引Chunk

Index_Chunk_Header
{
  u_int32     object_id;
  u_int32     size;
  u_int16      object_version;

  if (object_version == 0)
  {
    u_int32     num_indices;
    u_int16     stream_number;
    u_int32     next_index_header;
  }
}

num_indices是索引項的個數,stream_number指示對應的媒體流,next_index_header爲下一個索引Chunk基於文件首的偏移量。

IndexRecord
{
  UINT16   object_version;

  if (object_version == 0)
  {
    u_int32  timestamp;
    u_int32  offset;
    u_int32   packet_count_for_this_packet;
  }
}

這裏有一段用於解析Real Media文件的代碼。

3.2.4.2 打包和解包

作爲一種流媒體格式,與ASF相似,Real Media同樣是以包作爲基本的數據封裝單位,包頭附有時間戳,支持媒體幀的分割打包和組合打包。Real Media規定的包長最大不能超過65536字節,而且打在一個包內的媒體數據必須來自同一個媒體流。對於組合打包模式,Real Media要求每幀媒體數據之前附上的數據幀長度和時間戳;對於分割打包模式,則要求附上整個數據幀的長度和當前數據在整個數據幀中的偏移。

Media_Packet_Header
{
  UINT16                object_version;

  if ((object_version == 0) || (object_version == 1))
  {
    UINT16        length;
    UINT16        stream_number;
    UINT32        timestamp;
    if (object_version == 0)
    {
      UINT8        packet_group;
      UINT8        flags;
    }
    else if (object_version == 1)
    {
      UINT16        asm_rule;
      UINT8         asm_flags;
    }

    UINT8[length]        data;
  }
  else
  {
    StreamDone();
  }
}
3.2.4.3 邏輯流

RealMedia格式支持邏輯媒體流的概念,以實現對多個物理媒體流進行分組。一個邏輯媒體流擁有自己的Media Properties,其中會給出該邏輯流所包含的物理流的編號、數據位置等信息。假如一個RealMedia文件包含一個視頻流和一個邏輯流,該邏輯流由兩個不同比特率的音頻流組成,則該文件要包含四個Media Properties頭,分別對應一個視頻流、兩個音頻流和一個邏輯流,但只包含一個視頻流和兩個音頻流的數據包。

邏輯流Media Properties中的type_specific_data是如下一個結構:

LogicalStream
{
        ULONG32                                         size;
        UINT16                                          object_version;

        if (object_version == 0)
        {
                UINT16                                  num_physical_streams;
                UINT16                                  physical_stream_numbers[];
                ULONG32                                 data_offsets[];
                UINT16                                  num_rules;
                UINT16                                  rule_to_physical_stream_number_map[];
                UINT16                                  num_properties;
                NameValueProperty                       properties[];
        }
};

3.2.5 MKV

MKV是俄羅斯人於2002年發起的一個開放標準,2010年成爲WebM格式的基礎,藉助HTML5的興起,有望成爲目前流行格式FLV/F4V的有力競爭對手。

3.2.5.1 文件結構

MKV文件的基本數據單元叫做Element,每個Element依然以ID/Size的形式開始,不同的是ID/Size採用可變長度的EBML編碼。

和一般的多媒體容器類似,MKV定義了SEGMENTINFO來承載文件信息,定義了TRACKS/TRACKENTRY來承載媒體流的信息,定義了CUES來承載索引信息。除此之外,MKV還定義了CHAPTERS來支持類似於DVD的章節功能,定義了ATTACHMENTS允許將文件作爲附件。

MKV採用二級結構存儲媒體數據,首先,MKV文件中包含有多個CLUSTER,而每個CLUSTER包含若干BLOCKGROUP,BLOCKGROUP內的BLOCK Element存儲一個或多個媒體數據幀以及某些附加信息。承載不同媒體流的數據的BLOCKGROUP在CLUSTER中交錯存放,CLUSTER首部會給出一個時間戳,作爲其內部各BLOCKGROUP中的媒體數據的時間信息的基準。爲了減少數據量,也可以不使用BLOCKGROUP而將BLOCK直接存放在CLUSTER中,這種情況下的BLOCK稱爲SIMPLEBLOCK。

3.2.5.2 索引和隨機訪問

在MKV文件中,每個索引項由一個CUEPOINT表示,其中包含一個CUETIME和多個CUETRACKPOSITIONS,CUETIME表示當前索引項對應的時間點,CUETRACKPOSITIONS則給出該時間點上某個媒體流對應的媒體數據的在文件中位置,每個CUETRACKPOSITIONS包含track號,目標CLUSTER相對於文件的偏移量,目標BLOCKGROUP在CLUSTER中的編號。通常,MKV只對關鍵幀作索引。

3.2.5.3 章節信息
3.2.5.4 打包和解包

通常,MKV文件的媒體數據經由CLUSTER和BLOCKGROUP二級封裝,如下:

CLUSTER
    Timecode(uint)
    Postion(uint)
    Prevsize(unit)
    BLOCKGROUP
        Reference(int)
        Duration(int)
        BLOCK
            Tracknumber(vint)
            Timecode(sint16)
            Flags(int8)
            Framedata

CLUSTER首部的Postion給出了該CLUSTER相對於SEGMENT數據起始的偏移量,Prevsize給出了上一個CLUSTER的字節數(含ID/Size部分),爲文件損壞的情況下進行重同步提供了有效的線索。

BLOCKGROUP中的Reference以相對時間的形式給出了當前媒體幀對其它幀的依賴關係,Duration爲媒體數據的持續時間(一般用於字幕)。

BLOCK中的Tracknumber標識媒體數據所屬的媒體流;Timecode是媒體數據的時間標籤,相對於CLUSTER中的Timecode。Flags各位元定義如下:

Bit 0x80: keyframe:
          No frame after this frame can reference any frame before
          this frame and vice versa (in AVC-words: this frame is an
          IDR frame). The frame itself doesn't reference any other
          frames.
Bits 0x06: lace type
           00 - no lacing
           01 - Xiph lacing
           11 - EBML lacing
           10 - fixed-size lacing
Bit 0x08 : invisible: duration of this block is 0
Bit 0x01 : discardable: this frame can be discarded if the decoder 
           is slow

其中,Lace允許將多個媒體幀封裝到一個BLOCK中。如果使用Lace,則緊跟BLOCK首部信息的是一個標識總幀數的字節,然後是一系列幀長度的信息,語法因Lace方法不同而不同,然後纔是媒體幀數據。

MKV CLUSTER也有簡化的語法,使用SIMPLEBLOCK代替BLOCKGROUP:

CLUSTER
    Timecode(uint)
    Postion(uint)
    Prevsize(unit)
    BLOCK
        Tracknumber(vint)
        Timecode(sint16)
        Flags(int8)
        Framedata
3.2.5.5 Divx HD
3.2.5.6 WebM

3.3 同步問題

媒體同步主要涵蓋了三個方面的內容,其一是媒體自身的同步,即保持媒體樣本在時間軸上的相對關係,以滿足受衆的感知要求,譬如需要按照正確的採樣率播放一段聲音,如果採樣率不對,受衆聽到的聲音會走樣;其二是媒體之間的同步,這是爲了保證媒體之間的時間關係,譬如脣音同步:聲音和口型要對得上,也即聲音和圖像的播放在時間軸上要保持一致。最後就是發送端和解收端的同步,這是一種最強烈的同步,要求接收端各媒體在時間軸上的分佈與發送端保持完全一致,譬如電視直播應用中,各個接收端播放某個媒體樣本的時刻必須一致。除直播和實時通信以外的多媒體系統通常只需要滿足前兩種同步關係既可。

3.3.1 同步機制的實現

首先探討如何實現媒體自身的同步要求,主要是聲音和視頻。由於兩種媒體的播放設備存在着差異,其同步機制也不盡相同。聲音數據的同步控制和播放通常完全由硬件實現,音頻芯片有內置的時鐘,只要採樣率設置正確,數字樣本可以被準確地轉換爲模擬音頻。爲了保持一定的播放速度,音頻芯片需要源源不斷地讀取數據到其內置緩衝中,如果不能及時拿到數據,會出現緩衝下溢,而如果緩衝滿的情況下向音頻芯片寫入數據,則會出現緩衝上溢。所以,音頻芯片驅動通常會提供回調機制,應用程序利用這種回調機制發送數據給硬件,這也是一種典型的“拉”的數據傳輸方式,可以有效避免上下溢的發生。視頻則不然,視頻設備只提供一個用於更新圖像的幀緩衝,圖像數據需要在一個外部定時器的控制下寫入幀緩衝中,寫入的時刻也需要由外部控制。

  • 音頻同步機制一(回調方式):

當音頻播放設備的數據緩衝快要空的時候會激活某種回調機制,從而使上層註冊的回調函數得以調用。回調函數負責從本地數據緩衝拷貝數據到音頻設備緩衝,如果本地數據緩衝空則啓動解碼過程,以獲取更多的數據。在這一過程中,音頻設備控制着音頻播放的同步,假如回調函數因某種原因沒有及時拷貝數據,則音頻設備發生緩衝下溢,體現爲聲音輸出的停頓。

  • 音頻同步機制二(輪詢方式):

在這種方式下,音頻數據的寫入由獨立的線程來實現,該線程定時檢查本地數據緩衝和設備緩衝的水平,然後根據情況從解碼隊列中獲取音頻數據或將本地數據寫入到設備緩衝。在這種方式下,如果不能及時獲取解碼數據,則會導致音頻設備緩衝下溢,體現爲聲音輸出的停頓。

  • 視頻同步機制(輪詢方式):

視頻的同步方式略有不同,完全依賴於外部時鐘。這種方式也需要啓動一個定時器,定時檢查解碼隊列,如果解碼隊列中有圖像,則比較該圖像的時間戳和當下的系統時鐘,如果系統時鐘已經到了播放的時間,則拷貝該圖像到幀緩衝中。而如果系統時鐘遠遠超過了圖像的時間戳所指示的時間,說明發生了“圖像遲到”,體現爲屏幕上的畫面停頓。

再看音視頻間的同步。音視頻間的同步機制建立在音頻同步機制和視頻同步機制的基礎上,通常有以下三種策略:

  • 基於音頻時鐘的策略

這種策略下,音頻的同步方式可以採用以上的兩種方式的任意一種,視頻同步以音頻播放的時間爲準,即根據音頻播放的時間確定當前的圖像是否“遲到”。如果發生了“圖像遲到”,首先要將該圖像丟棄,同時啓動跳幀策略,通知解碼器以適當的頻率在解碼後直接丟棄圖像,直至重新恢復同步。如果“圖像遲到”是因某種突發情況產生的(解碼使用的CPU被突然大量佔用、媒體文件中暫時無法獲得視頻數據等),突發情況消失後調幀策略會加快解碼輸出速度,在一定的時間內重新恢復同步。如果音頻發生緩衝下溢,音頻時鐘會變慢,由於音頻時鐘同時是參考時鐘,會導致視頻的播放受到影響,如果緩衝下溢持續的時間很長,視頻播放會發生停頓,但下溢消失之後同步可以立即恢復。

  • 基於視頻時鐘的策略

這種策略下音頻同步以視頻播放的時間爲準,若本地緩衝中音頻數據的時間戳遠大於視頻時鐘,則暫停向音頻設備緩衝注入數據;若本地緩衝中音頻數據的時間戳遠小於視頻時鐘,則丟棄該數據,這兩種情況均會導致音頻設備緩衝下溢。第一種情況往往是由視頻方面獲取解碼數據時間過長引起的,因爲那會導致視頻時鐘變慢,第二種情況則是由於獲取音頻解碼數據時間過長引起的,這種情況下大量的音頻數據會被丟棄,需要較長的時間恢復同步。

  • 基於獨立時鐘的策略

這種策略採用一個獨立的時鐘作爲標準時鍾。對於視頻,如果發生了“圖像遲到”,丟棄該圖像之後立即調慢系統時鐘,雖然同時會導致聲音的緩衝下溢,但引發“圖像遲到”的因素消失之後同步迅速恢復。同樣,對於音頻,如果獲取音頻數據時間太長導致緩衝下溢發生,也可以立即調慢系統時鐘,雖然同時會導致圖像停頓,但恢復同步的時間大大縮小。

音頻設備緩衝下溢現象和視頻的“圖像遲到”現象都屬於媒體失步現象,一個好的多媒體系統首先要竭力避免這種情況發生,其次要具備發生失步之後儘可能快地恢復同步的能力,爲達到此目的,需要綜合傳輸協議設計、媒體容器設計、緩衝控制及同步策略等多個方面來設計系統。

3.3.2 失步的預防

上一節給出的策略不是爲了避免失步,而是爲了在發生失步的情況下恢復儘快同步。避免失步必須從協議、容器和緩衝策略幾個方面來考慮。 首先,在設計傳輸協議和媒體容器時,要儘可能保證音視頻數據適當的交錯,避免長時間無法獲取某種媒體數據的情況發生。這一條對於實時媒體的傳輸尤其重要,因爲緩衝策略會增加延時,無法應用於實時通信。其次,要根據傳輸協議構造一定大小的傳輸緩衝,以防止網絡抖動和音視頻交錯不好導致的媒體數據遲到;此外最好構造適當長度的解碼緩衝來保存解碼後的音視頻數據,以防止解碼抖動導致的媒體數據遲到。

3.4 流媒體技術

流媒體的概念仍舊可以追溯到模擬時代,且其內涵隨着通信傳輸技術的演進也在不斷變化。簡單講,流媒體技術的核心即是將多媒體信息從發端源源不斷地傳送到接收端並使其能夠在接收端持續播放,所以,模擬時代的電視、廣播都屬於流媒體的範疇。數字化革命發生之後,通信網絡中開始承載數字信息,發送端採集的數字音視頻信息經編碼器壓縮之後與控制信息複用到調制解調器的信道中,經由傳統電話網發送到接收端,最終由接收端解碼並播放,這便是ITU-H.324建議中描繪的可視電話及視頻會議應用場景,也算是數字流媒體的一個先例。然而,當通信網絡泛IP化之後,流媒體技術也隨之演進爲一種在IP網絡的基礎上實現數字媒體信息自發端到終端的持續傳輸的解決方案,通常體現爲一系列基於IP的傳輸協議和控制協議的組合。由於多媒體信息具有強烈的時間相關性,因此傳輸過程必須保證媒體的連續性,延時抖動必須得到嚴格的控制。爲了適應IP傳輸,流媒體格式出現了,首先是微軟的ASF,然後是RealNetworks的RM,它們共有的特性是將媒體數據如編碼的音頻幀或視頻幀封裝到包中,包頭附有時間標籤,以便實現媒體數據在IP網絡中的無縫傳輸以及接收端的亂序重組和丟包統計。

3.4.1 HTTP與RTSP

HTTP是IP網絡中佔據統治地位的應用層協議,這一點在今天來講是毋庸置疑的。數十年來,當初基於不同目的而設計的各種應用層協議不斷地被這一基於簡單的請求/應答機制的無狀態協議所替代。HTTP很簡單,但它可以實現文件傳輸、信息檢索、即時消息傳送,用戶會話、電子郵件等各種其他專有協議所負責的功能,雖然在性能上HTTP並非最優,但一個通用的應用協議對於構造一個富應用的下一代互聯網來說是非常必要的,這不僅可以降低網絡基礎架構及軟件平臺的成本,也能夠簡化應用開發的模型。

爲流媒體傳輸設計的RTSP便是這一泛HTTP化過程中的犧牲品。目前,互聯網上大部分視頻都是通過HTTP來進行傳輸的,自從HTTP 1.1增加Accept-Ranges支持文件的部分傳輸以克服其致命缺點之後,其剩餘缺陷如較大的頭開銷、重傳引起的延時、長時間緩衝造成的帶寬浪費等隨着基礎網絡的飛速發展變得不足爲道了。

RTSP採取的則是另一種策略:首先,它是個有狀態的,RTSP允許客戶端通過發送一系列初始化請求與服務器建立一個多媒體會話,會話建立之後,客戶端能夠通過發送播放或暫停命令啓動或停止服務器端的媒體推送。媒體的信息由服務器以SDP包的形式提供給客戶端,而媒體的傳輸由RTP/RTCP系列協議實現,由於RTP只是對UDP的一個簡單封裝,因此客戶端要負責RTP包的排序及丟包統計;媒體流的推送在服務器端完成,RTSP服務器負責解析目標媒體文件、抽取媒體流並打包發送,同時還需要根據以RTCP包回傳的統計信息調整推送策略。基於RTSP的流媒體傳輸具備帶寬開銷低,實時性好,延遲抖動小等優點,適用於各種專業的流媒體推送應用,尤其是基於IP網的流媒體直播。只是一直以來互聯網對RTSP並不友好,各種防火牆、代理服務器的存在爲RTSP的部署製造了重重障礙,再加上高性能的RTSP服務器成本高,構建複雜,RTSP並未得到廣泛的應用。而隨着HTTP對自適應流傳輸的支持逐步增強,RTSP的前景越發不被看好。

HTTP的另一個影響是流格式的式微:由於HTTP具備強大的傳輸控制能力,包的概念在多媒體文件格式中變得不再重要,先前爲了適應流傳輸而定義的包封裝對於HTTP來說成爲一種冗餘。事實上,就目前基於互聯網的多媒體應用來看,ISO文件格式遠比ASF流行。

3.4.2 基於HTTP的自適應流技術

欠實時性是HTTP流傳輸技術的致命缺陷,它限制了HTTP流傳輸在某些特定場合的使用,如直播、監控以及視頻會議等。此外,劇烈的帶寬波動常常致使網絡的瞬時帶寬降低至不足以承載正在傳輸的媒體的比特率,此時,大量的重傳反過來又加重了帶寬的負載,結果導致媒體播放發生頻繁的中斷,嚴重影響用戶的主觀感受。

蘋果公司使用了一種叫做HLS(HTTP Live Streaming)的自適應流傳輸技術來解決帶寬波動的問題。該技術要求服務器端提供內容相同但是比特率不同的多個備選媒體流,每一個媒體流都被分割爲大量小的MPEG TS文件,如是,在傳輸過程中,客戶端將不再請求一個大的媒體文件,而是不斷地請求分割開來的片段文件,因此,當帶寬發生變化時,客戶端可以平滑切換至比特率更合適的流,以降低帶寬的負載。通常,HLS自適應流的URL是一個播放列表,其中給出了媒體的基本信息以及各個流的描述,客戶端可以根據該列表發起傳輸請求、進行流的切換。

另一種相似的技術DASH(Dynamic Adaptive Streaming over HTTP)則是由MPEG發起的國際標準。之前另外兩個組織3GPP(3rd Generation Partnership Project)和OIPF(Open IPTV Forum)曾先後提出了Adaptive HTTP streaming (AHS)和HTTP Adaptive Streaming(HAS)技術,分別給出了針對手持設備和IPTV的自適應傳輸方案,這些方案成爲了DASH的基礎。正因如此,DASH同時支持MP4和MPEG2 TS兩種文件格式,而作爲國際標準,DASH也提供更多文件格式的擴展。

3.5 設備與接口

3.5.1 I2S

3.5.2 SPDIF

3.5.3 HDMI

HDMI是較早出現的一種數字傳輸接口,用於在一定距離內同步傳輸高品質的多媒體數據,如高解析度的原始數字視頻、打包的數字音頻及輔助數據等。通過HDMI接口,可以建立音視頻接收設備和顯示設備及音箱之間的純數字連接,此外,藉助於HDMI中繼器和交換器,還可以在家庭範圍內搭建一個數字化視聽系統,統一實現設備的控制和媒體的推送。

藉助於CEC系統構建的一個HDMI數字媒體網絡

HDMI接口由3個TMDS數據通道,1個TMDS時鐘通道,1個基於I2C的DDC通道,一根CEC連接線和一個熱插拔檢測腳組成。

3.5.3.1 TMDS
3.5.3.2 HDCP

HDCP協議實現了HDMI傳輸過程中的數字版權保護,以防止版權保護的數字影音在傳輸的過程中被非法截獲。相關的協議交互通過DDC通道完成,整個過程稍顯複雜:

首先,在決定是否發送媒體數據之前,HDMI發送設備會對接收設備或轉發設備進行認證,確認其爲經HDCP機構認證過的設備,對於未經認證的設備,發送端可以拒絕發送數據或僅傳送較低質量的視頻;其次,發送一幀視頻數據之前,發送端會對數據進行加密,並確認接收端能夠正確解密該數據,因此,TMDS信號傳送的實際上是加密的數據,理論上講直接截獲TMDS信號是無法重建原始視頻的;最後,HDCP協議還引入了黑名單機制,持續更新的黑名單記錄了被破解的設備,HDMI發送設備可以通過輸入的媒體數據獲取更新的黑名單。

協議的複雜導致實現的不便:哪怕是一個簡單的HDMI轉發器都需要至少一個微處理器用以實現協議交互,同時還需要具備一定的運算能力完成實時加密和解密。

3.5.3.3 CEC

CEC連接允許在兩個HDMI接口之間交換CEC控制信息,一個由HDMI連接構成的CEC系統能夠實現家庭視聽設備的統一管理和控制,包括下列功能:

  • 一鍵播放:允許一個播放設備喚醒電視並將該播放設備設定爲當前輸入。
  • 一鍵休眠:允許用戶通過一鍵促使家庭視聽系統中的所有設備都進入休眠狀態。
  • 一鍵錄製:允許用戶通過一鍵啓動相應的錄製設備開始錄製當前電視播放的內容。
  • 播放控制:允許用戶通過TV控制播放設備的播放動作,如播放、暫停、快進、後退等。
  • 調臺控制:允許用戶通過TV控制機頂盒的調臺功能,如換臺,切換模擬數字源等。
  • 聲音控制:允許音頻放大器充當TV當前輸入的聲音輸出設備,從而通過TV或STB的音量鍵實現音頻放大器的音量控制。
  • 遙控器轉發:允許TV將某些遙控器事件轉發給CEC系統中的某個特定設備。
  • 菜單控制:當TV在顯示某個輸入設備的菜單時,允許TV將當前遙控器的事件轉發給相應設備,從而實現利用TV的遙控器控制輸入設備的菜單。
  • 路徑控制:要求HDMI交換器能夠識別當前活躍設備並保證其與TV之間的連接。

四、壓縮技術

4.1 理論基礎

廿世紀中葉,爲了從理論上證明對信息系統進行優化的可行性,Shannon引入了熵的概念,用來表示信息的不確定性,熵越大,信息的不確定性就越大3),而信息的不確定性越大,其對應的傳輸和存儲成本就越高。換句話說,如果某種信息的熵不是那麼大,則人們應該有信心使用有限的資源去承載它。舉一個簡單的例子,假設氣象臺負責預報明天是否天晴,而地震局負責預報明天是否地震,那麼顯然,來自氣象臺的信息要比來自地震局的信息具有更大的不確定性,也就是說氣象信息的熵更大,如若使用喇叭來傳遞信息,對於氣象臺而言,以鳴喇叭來表示天晴或者表示天陰,對喇叭的使用壽命影響並不大,地震局則不然,如果以鳴喇叭來表示地震,那這喇叭的使用壽命遠大於氣象臺的那一隻。這說明,信息傳輸的成本是有下限的,這個下限由信源的熵決定,而如何達到或接近這個下限成爲通信領域的主要研究內容,數據壓縮便是其中的主題之一,在Shannon的通信模型中屬於信源編碼的範疇。

通過建立一個簡化的信源模型可以算出熵的最大值,這是非常有意義的,基於這個最大熵可以得到傳遞信息的極限成本。離散平穩無記憶信源就是這樣的一個簡化模型,源自這種信源的信息統計特性相同,但相互獨立,於是可以用一個概率空間[MP]來抽象這些信息,其中M={M1, M2, …, Mk}是概率分佈爲P={P1, P2, …, Pk}的一個隨機變量,那麼M的熵由下面公式給出:

H(M)=-Σ(Pi*log₂Pi)

公式中-log₂Pi表示Mi的信息量:概率越大信息量越小。於是不難發現,H(M)不過是信息量的一個概率平均。對於離散平穩無記憶信源,H(M)也可以看作信源的熵,針對某種特定的分佈,這個熵存在最大值,對應的分佈叫做最大熵分佈。離散無記憶信源的最大熵分佈是均勻分佈,此時其熵值爲log₂(k),k是其可能值的個數。

如果信源是有記憶的,也就是說信源產生的信息相互並不獨立,則需要引入聯合熵的概念。以兩個相關的隨機變量表示信源產生的兩個信息來構造一個最簡單的模型,以下三個公式成立:

H(X,Y) = H(X)+H(Y)-I(X,Y)           (4-1)
I(X,Y) = H(X)-H(X|Y) = H(Y)-H(Y|X)  (4-2)
H(X|Y) = H(X,Y)-H(Y)                (4-3)

其中,H(X,Y)爲聯合熵,表示這兩個信息整體上的不確定性;I(X,Y)爲互信息量,表示兩個信息的相關性,不相關的信息互信息量爲0;H(X|Y)叫做相對熵,表示在Y已知的情況下X的不確定性。第一個公式說明,對於相關的信息,其各自熵的和會大於描述其整體不確定性的聯合熵。第二個公式定義兩個信息的互信息量爲其中一個的熵減去其相對熵。第三個公式表示,X在Y已知的情況下的相對熵等於兩個信息的聯合熵減去Y的熵。

對於有記憶信源,其熵值不再等於其產生的某一個的信息的熵,這種情況下要使用熵率來描述信源的不確定性,這是一個極限值,假設Hn是信源產生的n個信息的聯合熵,則熵率就是n趨於無窮大時的Hn/n。

數據壓縮就是對信源產生的信息進行編碼的一個過程,即使用某個符號表,如0和1來表示要傳輸的信息。這裏涉及到兩種情形:無損編碼和有損編碼。對於無損編碼來說,要求解碼之後的信息和編碼之前的信息完全相同,即編碼過程不引入任何失真,在這種情況下,如果使用二進制符號來表示信源產生的某個信息,其平均長度不能小於信息的熵或信源的熵率。有損編碼則會在編碼過程中引入失真,因此,從根本上講是一個信息率-失真最優化的問題。

假設編碼過程引入的均方失真爲D,則存在一個函數R(D),表示不超過給定失真D的前提下對該信源編碼所需要的最小的信息率,即所謂的率失真函數。如果信源的概率分佈給定,平均失真D僅由信源編碼前後的轉移概率——亦即編碼方式決定,則率失真函數給出的其實是一個信源編碼的極限信息率,也就是說,對於既定信源,總可以找到一種編碼方式,能夠保證在既定失真的前提下達到率失真函數給出的最小信息率。率失真函數取決於信源的統計特性,一般不存在顯式的表達式,但是對於某些特定分佈的信源,率失真函數能夠以明瞭的形式給出,比如高斯分佈的連續無記憶信源的率失真函數爲:R(D) = ½ log₂(σ²/D) (0≤D≤σ²)。

再舉一個二值的離散無記憶信源X的例子:概率P分別爲0.1、0.2、0.3和0.5的情況下其率失真函數如下圖示:

可以發現,當P=0.5,即均勻分佈的情況下,信息率失真函數最靠上,也就是說給定最小失真對應的極限信息率越大。當失真爲零時,信息率的極限爲1,亦即信源的熵。也就是說,有這樣一個信源,它以50%的概率在產生符號0和符號1,則無失真地編碼該信源產生的一個符號最少也需要1個比特,注意,這是傳輸成本最高的一種信源。此時,我們便不難理解氣象臺的喇叭爲什麼更容易損壞了。

4.1.1 變換編碼

從理論上講,變換的主要目的是去相關。由公式4-1可知,對於相關性很強的兩個隨機變量,其互信息非常大,導致兩個信源的熵的和遠大於其聯合熵。如果將這兩個隨機變量看做爲一個二維的隨機向量,通過一個變換矩陣,可以將{X, Y}變換爲 {X', Y'},在這一過程中,H(X,Y)=H(X',Y'),如果變換矩陣選擇適當,令I(X',Y')=0,則H(X')和H(Y')將遠小於H(X)和H(Y),從而對X'和Y'編碼需要的比特數將大大減少。能夠使X'和Y'相互獨立的變換叫做KL變換,這是一種理論上的最佳變換,但由於相應的變換矩陣需要通過X和Y的統計特性來計算,在工程上很難應用起來。

可以從更直觀的角度來理解這種說法:以圖像數據爲例,假設每個像素點的亮度範圍爲0~255,則在空間域獨立地來看某個像素點的話,其統計特性是近似均勻的,也就是說,各階像素值發生的概率大致都差不多,因此,至少需要8個比特編碼一個像素值,而不能夠給某些像素值多些的比特,而給某些值少一些。但實際上,對於一個來自某幅圖像的像素矩陣採樣,如果其中某個像素點爲值0的話,其它點爲255的概率也不會太大。如下圖所示,左圖是典型的圖像數據(採樣自Lenna),而右圖則極爲罕見,在編碼的時候,左圖數據應該給以多於右圖數據的比特數,要達到這樣的目標,在缺少完美的矢量量化方法之前,變換不失爲一種很好的工具。

那麼,變換域中各點取值的概率分佈又如何呢?首先,各點的值域將發生變化,比如DCT變換域各點的取值範圍爲-2048~2048;其次,各點的概率分佈更加獨立。 還以上面兩幅圖像爲例,與罕見的圖像採樣(右圖)相比,經典的圖像採樣(左圖)變換域右下方向的值更接近0。因此,對於圖像數據來說,變換域右下角出現大量0值是比較常見的,可以使用更少的比特編碼這些大概率的情況。換句話說,變換是爲了從全局的角度抽取一組數據的特徵,並將這些特徵分割開來。

DCT是音視頻壓縮中的一種常用變換,它雖然不能保證使變換後的隨機變量相互獨立,但仍能大大減少它們的相關性,而且,DCT變換還能產生能量聚集的效果,即對於變換後的隨機向量,能量相對集中在索引較小的分量上,更有利於量化。

4.1.2 差分編碼

差分編碼基於預測來實現,即不編碼原始信源數據,而去編碼原始信源數據和預測數據的差分數據,主要目的是在不引入失真的前提下減小原信號序列的動態範圍。假設信源產生了一個隨機序列:

S(0), ..., S(n-5), S(n-4), S(n-3), S(n-2), S(n-1), S(n)

設S'(n)爲S(n)的預測值:

S'(n) = f(S(n-1), S(n-2), S(n-3), S(n-4), S(n-5))

則預測值序列爲:

S'(0), ..., S'(n-5), S'(n-4), S'(n-3), S'(n-2), S''(n-1), S(n)

令d(n) = S(n)-S'(n),則差分序列爲:

d(0), ..., d(n-5), d(n-4), d(n-3), d(n-2), d(n-1), d(n)

預測的準則是均方誤差最小,及找到一個合適的預測函數f,使d²(n)最小。 與變換編碼不同之處是,,即使找到了一個最優的預測函數f,差分編碼也不一定會提高編碼效率。如果隨機序列中個分量不具備相關性甚至是負相關的,差分序列中個分量的均方差會變得很大,甚至大於原始序列中各分量的均方差,這種情況下編碼效率會嚴重下降。

對於一個相關係數接近1的馬爾可夫序列,S'(n)=S(n-1)是一個較優的預測函數,這種差分編碼便是廣泛使用的DPCM技術。

需要注意的是,在實際的編碼過程中,由於解碼端無法得到原始值,所以預測函數通常使用預測值來代替原始值,即:

S'(n) = f(S'(n-1), S'(n-2), S'(n-3), S'(n-4), S'(n-5))

對於DPCM,S'(n)=S'(n-1)。

4.1.3 熵編碼

通過變換和預測等方法使信源的統計特性得到一定的改善之後(去相關性,降低均方差……),接下來需要進行的是熵編碼,也是數據壓縮的最後一步,其主要責任是將壓縮視頻的各種頭信息、控制信息以及變換系數轉換爲二進制的比特流。有兩種最基本的熵編碼方法:定長編碼和變長編碼,前者對所有的待編碼信息使用相同長度的碼字,後者則使用不同長度的碼字。假設某個待編碼的信息元素A∈{A0, A1, …, An},如果採用定長編碼,需要的比特數爲log₂(n)取整,而使用變長編碼,其平均碼長的極限取決於該信息的熵H,除非是均勻分佈,不然所需要的比特數必定小於log₂(n)。因此,如果某個信息元素在概率分佈上是極度不均勻的,通常都會採用變長編碼的方式,即概率小的值使用長碼字,概率大的值使用短碼字。

指數哥倫布碼、Huffman編碼和算術編碼都是常用的變長編碼方法。

4.1.4 量化

量化4)是一種多對一的映射,是引入失真的一個過程,也是限失真信源編碼技術的基礎。無論是對時間採樣後的模擬信號進行數字化的過程,還是對數字序列進行有損壓縮的過程,都需要完成一個由輸入集合到輸出集合的映射,這個映射是由量化來實現的。

最簡單的量化方法是將單個樣本的取值進行量化,因爲被量化的變量是一維的,所以這種量化方法叫做標量量化。設n階標量量化器的輸入爲連續隨機變量x,輸出爲離散隨機變量y,其中:

x∈(A0, An), y∈{Y1, Y2, …, Yn},A0≤ Y1 ≤ A1 ≤ Y2 ≤ …… An-1 ≤ Yn ≤ An。

則y 的取值由下式決定:

y = Yi 若Ai-1≤x<Ai

當量化階數n一定時,選擇合適的Ai 和Yi 可以使量化器的平均失真最小,這時的量化稱爲最佳標量量化。若輸入變量x滿足均勻分佈,可以將(A0, An)均勻分割成n個小區間,每個小區間的中點作爲量化值。這種量化方法叫做均勻量化,對於均勻分佈的輸入變量來說,均勻量化是最佳標量量化。當採用均方失真函數時,可以計算出其平均失真爲Δ²/12,其中Δ = (An-A0)/n。

然而,從率失真的角度來考慮,最佳標量量化並不能達到最佳率失真編碼的要求,通常需要對量化後的數據進行繼續進行處理,如熵編碼等。

爲了使量化後不再進行後處理而能逼近率失真函數的界,人們開始探討根據多個連續信源符號聯合編碼的方法,即矢量量化技術。假設X = {X1, X2, …, Xn}是信源的一個n維矢量,它的取值範圍是n維空間中的一個區域Rn,一個k級的矢量量化器就是X∈Rn到k個n維量化矢量Y1, Y2, …, Y的映射函數Q(X)。對於任意Yi,i = 1, 2, …, k,指定一個n維的區域Ai,對於所有X∈Ai,有Q(X) = Yi。其中Ai稱爲Yi的包腔,各量化矢量稱爲碼字,它們的集合稱爲碼書。如果選擇的碼書和各包腔可以使平均失真最小,這時的矢量量化稱爲最佳矢量量化。

4.1.5 率失真優化

率失真函數給出了信源編碼的信息率極限,而率失真優化則研究如何達到該極限,即在給定信息率上限Rc的前提下,尋找一種編碼方法使D最小化:

min{D(P)} s.t. R(P) ≤ Rc

其中,P表示一個信源編碼前後的轉移概率,代表某種編碼方法。

這是一個典型的有約束的非線性規劃問題,可以通過拉格朗日乘子法轉化爲一個無約束的求極小值的問題:

min{D(P)+λ*R(P)}

這裏的λ與約束條件Rc息息相關,目標速率越大,則λ越小,當λ爲零時,表示不限制目標速率,則只剩下min{D(P)}了。

實際的編碼過程不是數學推理過程,無需對上面的方程求解,只要確定了λ(這是個關鍵點),通過窮舉搜索即可找到最佳的編碼方法。

4.2 圖像

4.3 視頻

4.3.1 混合編碼系統

所謂混合指的是運動預測差分編碼和變換編碼的混合,其一般編碼原理由上圖給出,通常是以宏塊爲單位按照掃描順序進行編碼,當然也不排除基於某種大規模並行運算的編碼方法改掉這一方式。整個處理過程中,涉及到的數據包括:

  • P: 預測值
  • D: 差分值
  • D': 本地恢復的差分值
  • X: 量化後的殘差變換系數
  • R: 經過熵編碼的殘差變換系數

主要的處理模塊包括:

4.3.1.1 ME:運動估計

這裏的運動估計指的是爲待編碼宏塊中的各個像素點尋找最佳預測值的搜索過程,找到的預測值位於某一幀已經編碼並重建的圖像中,與被預測的像素點在位置上存在偏差,這些偏差就叫作運動矢量,它們和參考幀的位置共同作爲運動估計過程的最終輸出物。

4.3.2.2 MC: 運動補償

運動補償也叫做運動補償預測,這一過程根據運動估計給出的運動矢量和參考幀位置生成待編碼宏塊各像素點的預測值。由待編碼圖像和經運動補償的參考圖像逐點相減可生成一副差分圖像,相對與自然圖像,差分圖像的動態範圍大大減小,從而更有力於後續的壓縮

4.3.2.3 T: 變換

通過對待編碼像素與預測值的差值進行一個二維變換,有效地去除空間冗餘。

4.3.2.4 Q: 量化

變換系數的量化通過引入失真降低比特率。

4.2.3.5 1/T: 反變換

這一過程和下一過程的主要目的是生成本地重建圖像。由於解碼器端無法獲取原始圖像,爲了防止誤差積累,需要在編碼器端複製解碼過程,使用解碼後的重建圖像代替原始圖像進行預測。

4.2.3.6 1/Q: 反量化
4.2.3.7 Zigzag: Z掃描

Z掃描可以將二維的殘差變換系數轉換爲一維的序列,更有利於其後的熵編碼。

4.2.3.8 Entropy: 熵編碼

利用數據流內部的統計特性對一維的殘差變換系數進行無損壓縮。

4.2.3.9 Filter: 環內濾波

環內濾波有兩個目的:其一是爲了去除因變換產生的塊效應;其二是通過改善重建圖像,使預測過程更有效。

4.2.3.10 Intra: 幀內預測

在某些情況下,無法獲得參考幀,或參考幀中的預測值與實際值的差距過大(比如視頻序列發生場景切換),則不採用已編碼重建圖像中的像素值作爲預測值,而以當前圖像中已編碼部分的像素值作爲預測值。

4.2.3.11 模式選擇

此外,編碼過程還隱含着一個模式選擇的模塊,體現在上圖中就是決定幀內還是幀間的開關,而實際上除了這個的開關,其他諸如運動補償、幀內預測都要涉及到模式選擇,譬如基於多大的塊進行運動補償、使用哪一個參考幀等。

運動補償預測可以用下圖來表示: 

紅色部分爲待編碼的宏塊,彩色部分爲利用運動補償由本地解碼的重構圖像生成的預測值,而最終編碼的是紅色部分和彩色部分的差值。可以看出,爲了完成該宏塊的預測,需要四個運動矢量和兩個參考幀。某些編碼技術如雙向預測及MPEG2中的Dual-Prime會令預測過程更加複雜,預測值需要由兩個經運動補償的預測值加權平均得到: 

4.3.2 MPEG-2視頻

MPEG-2視頻的官方代號爲ISO 13818-2,是ISO針對數字存儲和數字電視應用提出的視頻壓縮標準,ISO同時在13818-1中給出了相應的傳輸標準,通常,MPEG-2視頻的傳輸都在13818-1規定的框架內進行。

MPEG-2的制定是在H.261的的基礎上完成的,主要特徵爲:

  1. 基於序列/圖像組/圖像/條/宏塊/塊(Sequence/GOP/Picture/Slice/MB/block)的組織方式;
  2. 逐行序列和隔行序列(Progressive/Interlaced Sequence)的支持;
  3. 幀圖像和場圖像(Frame Picture/Field Picture)的支持;
  4. 更多的運動補償方式
  5. 半像素精度運動補償的支持
  6. I/P/B三種圖像編碼方式
  7. 8×8 DCT

其中包括了一些新的技術點:

對隔行掃描的支持

爲了兼容模擬時代不得以引入的隔行掃描技術,MPEG-2增加了對隔行掃描圖像序列的支持。MPEG-2的序列頭中有一個progressive_sequence標誌,progressive_sequence爲1表示逐行序列,progressive_sequence爲0表示隔行序列。隔行序列就是採用隔行掃描的技術產生的圖像序列,其中的每個掃描圖像叫做一個場,它們或者掃描自奇數行,或者掃描自偶數行,分別叫作頂場和底場,整個序列中頂場和底場按照採樣時間交錯排列。

逐行序列的圖像編碼方式基本上同H.263相同,是以幀爲單位的,圖像頭中的picture_structure字段永遠爲3;frame_pred_frame_dct字段永遠爲1。但對於隔行序列,MPEG-2既支持以幀爲單位進行編碼,也支持以場爲單位進行編碼。所謂以幀爲單位進行編碼是指將兩個場圖像合併爲一個幀,解碼輸出時再將其拆作兩個場,圖像頭中的top_field_first字段用以指示輸出場的順序。如果是以場爲單位進行編碼,一幅圖像就是一個場,圖像頭中的picture_structure字段可以爲1和2,分別表示頂場和底場。事實證明,對於某些近乎靜止的視頻片段,即使採用隔行掃描,以幀爲單位進行編碼也比以場爲單位效率更高。

對隔行序列的支持也引入了更復雜的預測方式和DCT方式:

對於以場爲單位進行編碼的圖像,其運動補償預測都是基於場的,參考圖像可以從已完成解碼的兩個場中選擇,既可以是和當前場採樣位置相同的場,也可以是和當前場採樣位置相反的場。其次,新增加了16×8的運動補償方式,此時一個宏塊會包含兩個運動矢量——B幀的話可能出現四個,而每個運動矢量都可以選擇與當前場採樣位置相同或相反的場作爲參考。此外,基於場的預測還支持Dual-Prime的方式,這種方式允許同時使用兩個採樣位置相反的參考場進行平均預測,其中基於和當前場採樣位置相同的參考場的運動矢量需要包含在碼流中,而基於和當前場採樣位置相反的參考場的運動矢量以在前者基礎上的校正值的形式傳輸,校正的範圍至多爲1個像素。

對於以幀爲單位進行編碼的圖像,每個宏塊既可以採用幀預測方式實現運動補償,也可以採用場預測方式進行運動補償,如果採用場預測方式,頂場和底場各需要一個運動矢量(B 幀要兩個),而且每個運動矢量可以選擇使用它們的各自的參考場。當然,以幀爲單位進行編碼的圖像也可以使用Dual-Prime場預測,此時一個宏塊只包含一個運動矢量和一個校正值,上下兩個場都依據此運動矢量和校正值完成相應的Dual-Prime預測和運動補償。DCT也類似,如果採用場方式DCT,亮度四個DCT塊的組織方式就會變成:上半場左側、上半場右側、下半場左側、下半場右側。這裏,預測方式和DCT方式的選擇都是以宏塊爲單位選擇的,分別由宏塊頭的frame_motion_type,和dct_type字段指定,前提條件是圖像頭擴展中的frame_pred_frame_dct設爲了0。

量化係數矩陣及步進控制

標準規定了缺省的量化矩陣,但也允許編碼器發送自定義的量化矩陣,這些數據可以插入到序列頭或Quant matrix extension中,YUV420格式的視頻序列需要兩個8×8的矩陣,分別用於幀內編碼和幀間編碼,YUV422和YUV444格式則需要四個,因爲亮度信號和色差信號使用不同的矩陣。對於幀內的直流係數,量化因子幾乎是固定的,只能取1、2、4、8四個值,在圖像頭擴充中指定,而且不允許步進變化。對於其他係數,允許在量化矩陣的基礎上做伸縮,伸縮因子可以在slice層面完成控制,也可以在macroblock層面完成控制。

後向預測和B幀

後向預測允許使用當前編碼圖像在時間軸上向後的圖像作爲運動補償預測的參考圖像,這可以有效地解決運動掩蓋問題——被運動物體掩蓋的部分會在將來重現於畫面,而這部分內容很難在過去的圖像中找到對應的參考預測區域。於是,MPEG-2中首次提出了既可以使用前向預測也可以使用後向預測的B幀,編碼一個B幀需要兩幅參考圖像,一幅取自過去,作爲前向預測的參考圖像,另一幅取自將來,作爲後向預測的參考圖像。此外,B幀還允許同時使用兩幅參考圖像,最終的預測值由分別來自兩幅參考圖像的預測值取均值得到,這種預測方式被稱爲雙向預測,雙向預測需要兩個運動矢量來進行運動補償,一個用於尋找前向預測值,另一個用於尋找後向預測值。MPEG-2標準規定B幀以宏塊爲單位選擇預測方式,編碼器根據待編碼宏塊的圖像內容決定使用前向預測、後向預測還是雙向預測。

B幀帶來的好處主要有兩方面:首先,前向預測和雙向預測技術可以明顯改善某些特定內容的編碼效果;其次,MPEG-2中的B幀是不作爲參考圖像參與預測編碼的,因此,B幀的損壞不會引起錯誤擴散,在某些特定的情形下——如出現傳輸和處理延遲——也可以通過丟棄B幀來加快處理過程。當然,引入B幀也要付出一些代價,除了增加運算複雜度之外,解碼器端需要多的圖像緩衝,爲了調整解碼圖像的順序使之與顯示順序保持一致,解碼器會產生一定的輸出延遲。

伸縮編碼

MPEG-2是第一個引入伸縮編碼的標準。伸縮編碼亦可理解爲分層編碼,其目標是允許客戶端根據自己的能力和網絡環境選擇接收適合自身的視頻子流。伸縮編碼通過在一個高質量的視頻流中嵌入一個或多個較低質量的視頻子流來實現,其中最基礎的低質量視頻子流可以有解碼器獨立解碼,而高質量的子流則需要在低質量子流被解碼的基礎上實現解碼。標準提供了三種實現方法:

  • SNR伸縮

在編碼過程中,通過將本地解碼得到的重構圖像與原始圖像做差分,然後將差分數據再次進行DCT變換、可以得到一個增強的視頻碼流。在傳輸過程中,增強流和基礎視頻流同時傳輸,於是在解碼器端,單純解碼基礎流可以生成較低質量的視頻數據,而增強流和基礎流合併解碼則可以生成較高質量的視頻數據。

  • 時間伸縮

由對原始輸入視頻進行幀率的降採樣來實現。其中,基礎視頻流通過對降採樣的視頻序列進行標準編碼得到,而增強流通過對幀率降採樣過程中丟掉的那些圖像進行增強編碼得到,增強編碼必須使用基礎流的重建圖像作爲參考圖像。

  • 空間伸縮

由對原始輸入視頻進行空間的降採樣來實現,其中,基礎視頻流通過對降採樣後的低分辨率視頻序列進行標準編碼得到,而增強流通過對全分辨率的視頻序列進行增強編碼得到,增強編碼必須使用基礎流的重建圖像作爲參考圖像。

可自定義的運動矢量範圍

MPEG-2標準可以根據視頻序列本身的特徵確定運動矢量的範圍,從最小的-8~7.5到最大的-2048~2047.5,由picture_coding_extension中的f_code標識。對於不同的運動矢量範圍,運動矢量自身的分辨率也不同,範圍越大粒度越粗。

MPEG-2標準首次提出了profile和level的概念,profile一般被譯爲類或者檔,每一種檔都會規定一個可使用的編碼技術的集合,同時對應一個確定的碼流兼容標準,從而在保持標準兼容性的同時增強了靈活度;level通常譯爲級,來指示對解碼器運算資源的要求,涵蓋了對碼流的比特率、圖像的大小、運動矢量的範圍等各種規定。MPEG-2定義了五個檔和四個級別,它們之間可以自由組合,以表示解碼器解碼能力的要求。而作爲一種通用的概念檔和級別也自此被沿用到後續的標準中。

現在來看,MPEG-2算不上是很出色的視頻壓縮技術(在某些地方對比特的使用近乎奢侈:譬如換行時運動矢量的預測值並不重置等),卻是一種在商業應用上非常成功的一種技術,同時也是很昂貴的一種技術,:2002年前每臺設備的專利費爲4到6美元,後來降至2.5美元,最近又降到2美元,由於其在DVD和數字電視中的廣泛應用,非MPEG-LA專利持有者的設備商和內容提供商不得不爲使用此技術支付鉅額的專利費用。

4.3.3 H.263

H.263是其前身H.261——第一個真正意義上的視頻壓縮標準——的增強版,由ITU制定,側重於低碼率視頻應用環境,如基於老式電話網絡的視頻會議和可視電話等。通常意義上的H.263指的是1995年定稿的版本,對應的文檔按照ITU的慣例以建議的形式發佈。這個版本的H.263除了定義編解碼的主框架之外,還附帶8個附件,其中有4個屬於對主框架算法改進的選項。由於H.263在制定之初就具有很強的針對性,即服務於甚低帶寬的老式電話網,其性能的優勢也主要體現在低比特率的應用場景。直到後來,ITU不斷以附件的形式對基礎算法進行修訂和增強,產生了H.263+、H.263++等諸多版本,才逐步使H.263系列技術的應用範圍得到更大的擴展。

H.263最初的版本規定輸入圖像必須使用YUV色彩空間,即每幅圖像由一組亮度信號採樣和兩組色差信號採樣組成,考慮到人眼對顏色信號不甚敏感,色差信號的採樣頻率只有亮度信號的四分之一,這樣從源端即可以減掉一部分數據量。編碼過程則以宏塊爲基本單位,每個宏塊包含一個16×16的亮度信號採樣和兩個8×8的色差信號採樣。DCT變換以8×8的塊爲單位進行,主算法以16×16的塊爲單位對亮度信號預測進行運動補償,以8×8的塊爲單位對色差信號預測進行運動補償。

H.263的主算法中首次使用了小數精度的運動補償技術,規定運動矢量的基本單位爲半個像素。基於運動補償的預測算法可以由一個基本公式來描述:

Xp(x, y) = Xr(x+dx, y+dy)

其中,Xr(i,j)和Xp(i,j)分別表示參考圖像在某一特定位置(i,j)的像素值以及當前圖像在某一特定位置(i,j)的像素的預測值,(dx,dy)描述了一個二維的運動矢量。顯然,要在對應的參考圖像中得到Xr(x+dx, y+dy),必須要求dx和dy爲整數,否則,Xr的值必須通過插值才能得到,則以上公式演化爲如下形式,其中Dx和Dy爲dx和dy的整數部分:

Xp(x, y) = Σ{a(i,j)*Xr(x+Dx+i, y+Dy+j)}

此時圖像中某一個像素的預測值不再來自參考圖像中經運動補償的某一個點,而是由參考圖像中多個像素點加權平均得到。顯然,這是一種空間域的FIR形式維納濾波器,半像素只是其中的一個特例:

Xp(x, y) = a*Xr(x+Dx, y+Dy) + b*Xr(X+Dx+1, y+Dy) + c*Xr(x+Dx, y+Dy+1) + d*Xr(x+Dx+1, y+Dy+1)

參數a、b、c、d的取值由運動矢量的小數部分決定:

(0, 0) a=1, b=0, c=0, d=0
(0.5, 0) a=0.5, b=0.5, c=0, d=0
(0, 0.5) a=0.5, b=0, c=0.5, d=0
(0.5, 0.5) a=0.25, b=0.25, c=0.25, d=0.25

當然,由於涉及整除後小數取整的問題,建議中規定的係數與上表略有不同。

一些建議制定之初尚處於實驗階段的算法被以附件的形式添加到草案中,其中對性能提高有顯著幫助的主要有兩項:

4MV

使用4MV模式編碼的宏塊使用四個運動矢量完成運動補償,每一個運動矢量對應一個8×8的宏塊。關於究竟採用多大的塊來進行運動補償,學術界曾進行過一段時間的探索。理論上講,塊越小,對運動的描述越精確,如果每個象素點使用一個運動矢量的話,幾乎可以完美地描述兩幅圖像間的運動關係,可是,運動信息的編碼是需要佔用比特數的,因此需要在運動補償的精確度和運動矢量的編碼開銷上進行一定的權衡,早期的論文認爲最佳的塊尺寸是16×16或32×325)。但這樣的結論是很草率的,其前提是一個認爲畫面各區域運動情況基本一致的假設,而這一假設在大多數情況下都不成立:一組典型的運動畫面往往存在幾乎沒有變化的背景,滿足剛性運動的前景以及變化複雜的細節部分。因此,很難找出最佳的固定尺寸來定義實現運動補償的塊,而根據內容選擇塊的尺寸倒是一種最佳的解決辦法,4MV是基於這一解決辦法的最初探索。至於選擇4MV還是基礎的1MV來編碼一個宏塊,建議中沒有給出任何信息,而在內容識別算法取得突破性進展之前,這一選擇只能在後驗的基礎上做出,即分別使用4MV和1MV對一個宏塊編碼一次,哪種方式效果好就使用哪種。

PB幀

PB幀借鑑了MPEG-2中B幀的概念。但B幀的使用會增加額外的傳輸負擔,主要是更多的預測模式和運動矢量信息,這在低碼率傳輸環境中並不划算,所以H.263對這一技術進行了簡化。首先,只允許一種預測模式,即雙向預測;其次,儘量不傳輸運動矢量,而是根據時間關係來利用P幀的運動矢量來推算B幀的運動矢量;最後,B幀沒有自己的頭信息,也就是說讓一個P幀和其後的B幀使用相同的頭信息——包括圖像頭、宏塊頭等,但每一個宏塊內包含12個塊數據,分別屬於P幀和B幀。最後一點是至關重要的,否則,僅僅使用雙向預測得到的收益(這種雙向預測還並非真正意義上的雙向預測,因爲其運動矢量不是最優的)遠抵不上頭信息的開銷。

運動矢量的推算根據以下公式進行,顯然,其基礎是一個非常簡單的勻速剛性運動模型,就現實的運動畫面來講,符合此模型的情況極少,因而單純從預測的角度講,PB幀的效果並不一定比P幀更好,因爲至少使用全搜索的情況下P幀可以保證預測殘差值的絕對值和最小。

MVF = (TRB*MV)/TRD
MVB = ((TRD-TRB)*MV)TRD

PB幀的使用定義在附件G中,但是後來附件M對附件G做了修訂,進而將附件G廢棄。修訂的PB幀允許使用更多的預測模式,而此時的H.263也不再是一個純粹的針對甚低碼率應用的技術規範了。

4.3.4 MPEG-4

4.3.5 H.264

H.264的官方名稱是AVC(先進視頻編碼),對應的標準文檔是ISO 14496-10,因此也稱作MPEG4-AVC,是由MPEG和ITU合作制定的視頻編碼標準。從原理上講,H.264仍舊秉承傳統混合編碼架構,但由於在編碼細節引入和諸多改進,將許多一直停留在紙上的技術如可變尺寸塊的運動補償、多參考幀預測等付諸實踐,使編碼效率得到了顯著的提高,同時也大大增加了編解碼器的複雜度和運算量。總的來說,在圖像質量保持相同的條件下,H.264編碼需要的數據率可以小於MPEG-4的一半。

H.264主要引入瞭如下技術點:

4x4小矩陣變換

小的變換矩陣有時可以產生更好的壓縮效果,能夠在不增加比特率的情況下改善圖像質量,而且會減少方塊效應;此外,小的變換矩陣也可以降低運算量:如果不使用快速算法,8×8的二維變換總共需要1024次乘法(8x64x2),但4個4×4的二維變換值需要512次(4x16x2x4),16個2×2的變換256次。

基於此,H.264引入了4×4小矩陣變換的編碼選項,並對變換過程和量化過程進行了深度改造,儘可能摒棄其中的浮點數運算和乘除法操作,進一步降低了運算量,以至變換過程不再成爲編解碼運算的瓶頸。

假設H.264 4×4變換過程爲Y=FXF',其變換矩陣F簡化爲:

1  1  1  1
2  1 -1 -2
1 -1 -1  1
1 -2  2 -1

則整個變換的函數也可以簡化至:

void dct4x4(short dct[16])
{
    int i;
    int s03, s12, d03, d12;
    short tmp[16];
 
    for (i = 0; i < 4; i++)
    {
        s03 = dct[i*4+0] + dct[i*4+3];
        s12 = dct[i*4+1] + dct[i*4+2];
        d03 = dct[i*4+0] - dct[i*4+3];
        d12 = dct[i*4+1] 1 dct[i*4+2];
 
        tmp[0*4+i] = s03 + s12;
        tmp[1*4+i] = (d03<<1) + d12;
        tmp[2*4+i] = s03 - s12;
        tmp[3*4+i] = d03 - (d12<<1);
    }
 
    for (i = 0; i < 4; i++)
    {
        s03 = tmp[i*4+0] + tmp[i*4+3];
        s12 = tmp[i*4+1] + tmp[i*4+2];
        d03 = tmp[i*4+0] - tmp[i*4+3];
        d12 = tmp[i*4+1] 1 tmp[i*4+2];
 
        dct[i*4+0] = s03 + s12;
        dct[i*4+1] = (d03<<1) + d12;
        dct[i*4+2] = s03 - s12;
        dct[i*4+3] = d03 - (d12<<1);
    }    
}

顯然,矩陣F不具備正交性,因此以上變換並非正交變換,它只能作爲正交變換的一部分,整個正交變換其實爲:Y=[F•R]X[F'•R']=FXF'•[R•R'],其中,R爲:

0.5    0.5    0.5    0.5
0.3162 0.3162 0.3162 0.3162
0.5    0.5    0.5    0.5
0.3162 0.3162 0.3162 0.3162

R•R'爲:

0.25   0.1581 0.25   0.1581
0.1581 0.1    0.1581 0.1
0.25   0.1581 0.25   0.1581
0.1581 0.1    0.1581 0.1  

則真正的變換矩陣F•R爲:

0.5     0.5     0.5     0.5
0.6325  0.3162  -0.3162 -0.6325 
0.5     -0.5    -0.5    0.5
0.3162  -0.6325 0.6325  -0.3162

可以發現,這個矩陣已經接近正交——其實如果用sqr(10)/10代替0.3162、sqr(10)/5代替0.6325的話,該矩陣就是正交矩陣,再來看正宗的DCT變換矩陣:

0.5     0.5     0.5     0.5
0.6532  0.2706  -0.2706 -0.6532 
0.5     -0.5    -0.5    0.5
0.2706  -0.6532 0.6532  -0.2706

顯然,H.264使用的4×4變換矩陣實際上不過是DCT矩陣的一種近似——使用sqr(10)/10代替sqr(1/2)*cos(π/8)、使用sqr(10)/5代替sqr(1/2)*cos(3π/8),然而,這種近似可以將變換過程分離成爲一個子變換過程和一個係數抽取的過程,其中前者是一個整形化且剝離乘法的簡單運算;後者則可以合併到其後的量化過程中。至此,小矩陣變換的運算過程實現了一個完美的簡化。

逆變換X=[I•S]X[I'•S']的分解略有不同,其中I爲:

1    1    1   0.5
1    0.5 -1  -1
1   -0.5 -1   1
1   -1    1  -0.5

S爲:

0.5    0.6325 0.5    0.6325
0.5    0.6325 0.5    0.6325
0.5    0.6325 0.5    0.6325
0.5    0.6325 0.5    0.6325

S•S'爲:

0.25   0.3162 0.25   0.3162
0.3162 0.4    0.3162 0.4
0.25   0.3162 0.25   0.3162
0.3162 0.4    0.3162 0.4

而逆變換矩陣I•S爲:

0.5    0.6325  0.5  0.3162
0.5    0.3162 -0.5 -0.6325
0.5   -0.3162 -0.5  0.6325
0.5   -0.6325  0.5 -0.3162

不出所料,I•S是F•R的轉置,這符合正交變換逆變換的要求,但需要注意的是,逆變換的伸縮矩陣S•S'卻正變換中的R•R'存在差異,這導致H.264的量化和反量化過程相對之前的壓縮標準也有所不同。

在H.264中,對變換系數矩陣進行量化的步長是統一的,但由於伸縮矩陣的存在,每個量化步長對應着一個伸縮過的量化矩陣。比如量化步長爲1時的量化矩陣爲:

1/0.25   1/0.1581 1/0.25   1/0.1581
1/0.1581 1/0.1    1/0.1581 1/0.1
1/0.25   1/0.1581 1/0.25   1/0.1581
1/0.1581 1/0.1    1/0.1581 1/0.1  

變換系數需要除以量化矩陣中對應的量化係數以完成量化過程。爲了剔出除法運算,H.264定義了整型量化矩陣,變換系數需要先乘以該整型矩陣對應的元素,最後統一除以32768,則以上量化矩陣對應的整型量化矩陣爲:

8192 5243 8192 5243
5243 3355 5243 3355
8192 5243 8192 5243
5243 3355 5243 3355

同樣,對於步長爲1的反量化操作,反變換矩陣需要乘以伸縮矩陣,而由於伸縮矩陣中存在浮點數,則需要將該伸縮矩陣擴大64倍,反量化之後再將結果統一除以64。擴大後的反量化矩陣爲:

16 20 16 20
20 25 20 25
16 20 16 20
20 25 20 25

這個反量化矩陣定義於H.264的標準中,對應量化因子爲4。標準中共定義了6個基本的反量化矩陣,對應6個量化因子,而在這6個反量化矩陣的基礎上通過爲係數乘以2的倍數又可以形成更多的擴展反量化矩陣。標準中對量化矩陣沒有做定義,但根據反量化矩陣不難計算出對應的量化矩陣。

幀內預測

H.264中使用的幀內預測方法有兩個特徵:其一是基於空間域中的預測,其二是基於方塊的預測。被預測的方塊區域有4×4、8×8和16×16三種,預測值源於與方塊區域相鄰的上方和左方的邊緣像素,預測的方式有若干種(參見示意圖)。

幀內預測的使用使宏塊的解碼及宏塊內方塊的解碼具有相互的依賴性,要求宏塊解碼和宏塊內方塊的解碼遵循嚴格的自左至右、自上而下的掃描順序,這也成爲解碼過程並行化的一大阻礙。

在解碼過程中,反變換模塊輸出的殘差值加上預測值即可生成圖像數據的重建值,決定預測值的預測模式可以從碼流中獲取:對於16×16預測,預測模式信息包含於mb_type中,而對於8×8和4×4的預測,預測模式要專門給出,爲了節省比特,預測模式本身也是預測編碼的,即如果兩個相鄰塊的預測模式相等的話,置一個標誌爲1而無需給出後一個塊的預測模式。

多參考幀

早期的混合型視頻壓縮方案在編碼一個P幀時僅使用該幀在時間軸上向前最近的一個I幀或P幀作爲預測幀,編碼一個B幀則分別使用時間軸上往前和往後最近的一個I幀或P幀作爲前向預測幀和後向預測幀。而多參考幀技術則突破了這一限制,擴大了參考幀的選擇範圍:P幀可以使用當前幀前面一定範圍內任意一個I幀或B幀作爲參考幀,而B幀可以從當前幀之前和之後一定範圍內的I幀或P幀中分別選擇兩幀作爲前向預測幀和後向預測幀。最早使用這一技術的是H.263+,但參考幀的選擇依舊停留在圖像級別,這在一定程度上有助於抗誤,但就改善壓縮性能方面意義不大,H.264則將選擇參考幀的層次降低到預測塊級別,從而擴大了各個塊進行幀間預測的範圍,通過採用合適的率失真優化技術能夠使壓縮效率得到一定程度的提高。

運動預測單元的多樣化

早期的視頻壓縮技術統一使用16×16的矩形作爲運動預測的基本單元,這是一個折衷之後的經驗值,因爲預測單元越大,預測效果越差,但預測單元太小,承載運動信息需要的比特數又太多。在H.263+的Annex F中,提供了8×8運動預測的選項,使用8×8預測模式的宏塊需要4個運動矢量。

H.264提供了更多的選擇,包括16×8,8×16模式,以及8×8模式,對於採用8×8模式的子塊,還有四種子模式可供選擇,它們分別以8×8、4×8、8×4、4×4矩形作爲運動預測的基本單元。

四分之一像素精度的運動補償

運動補償像素精度的提高是通過對參考圖像進行內插實現的,非整數點位置的像素值可以由某種濾波器生成,而從理論上講整個過程可以被認爲是某種線性預測濾波,譬如對於四分之一像素精度來說,生成預測值的是一個四抽頭線性濾波器。

基於宏塊的幀場自適應(MBAFF)

爲了保持向前兼容,H.264沒有放棄隔行掃描的支持,一個slice可以是場圖像,也可以是幀圖像,對於幀圖像,H.264允許在slice之內自有切換幀場編碼模式,也就是幀場自適應技術。在使用幀場自適應的情況下,一個slice被劃分爲若干宏塊對,每個宏塊對包括上下相鄰的兩個宏塊,這兩個宏塊可以是幀編碼,也可以是場編碼。

基於上下文的熵編碼
環內濾波

H.264要求對重建圖像進行濾波,運動補償及預測必須使用濾波後的圖像作爲參考圖像,由於這一過程是嵌入在編碼循環之中的,因此稱爲環內濾波,以區別於簡單的後處理平滑濾波。引入環內濾波有兩個初衷:

  1. 消除方塊變換引起的塊效應,從而改善輸出圖像的主觀質量。
  2. 通過使用平滑後的圖像作爲運動補償和預測的參考圖像,來提升預測編碼的率失真性能。

需要注意的是,爲了防止濾波過程消除圖像中本來就有的邊緣信息,必須設定合適的濾波閾值,只有塊邊界兩側像素點的差值小於設定的閾值時才進行濾波,而這個閾值又往往與量化級相關,量化級越大,閾值也就越大。

濾波過程以宏塊爲單位按照掃描順序進行,如果使用了8×8變換,需要對兩個垂直邊緣和兩個水平邊緣進行濾波,如果使用了4×4變換,需要對四個垂直邊緣和四個水平邊緣進行濾波,濾波的順序爲先垂直、後水平。濾波強度由小到大分爲四級,一到三級使用四個抽頭,最多修改四個像素;第四級使用五個抽頭,最多修改六個像素。強度的選擇取決於邊沿兩側數據的編碼方式,譬如對於宏塊邊緣且邊緣兩側有幀內編碼的情況濾波強度爲最強的四級。

由於影響濾波的因素很多,每修改一個像素點都需要做複雜的判斷,這導致環內濾波成爲H.264壓縮技術中運算量最大的過程之一。而爲了節省內存空間,要求環內濾波可以就地執行,也即濾波的結果同時也會作爲濾波的輸入,因此,環內濾波的執行具有強烈的順序性,首先上面一行的宏塊濾波結束後才能開始下面一行宏塊的濾波,一行宏塊的濾波必須自左至右執行;其次宏塊內各邊緣的濾波必須按照先垂直後水平、自左至右、自上而下執行,這樣爲大規模的並行處理造成了一定困難。從上圖可以看出,e,f,g的濾波結果依賴於另一個宏塊,h和i的濾波依賴於g的濾波結果,則也依賴於另一個宏塊,其中g的最終結果需要經過兩次濾波才能得到。

與以往的MPEG標準類似,H.264也定義了不同的檔和級別,對編解碼器需要支持的編解碼技術和運算能力作出了規定。

在ISO標準的2011年修訂版中,定義了多達11個檔及17個級別。其中,最常見的檔有基礎檔(base profile),主檔(main profile)和高級檔(high profile),級別則從1.0一直到5.2。在基礎檔中,除了不能使用B slice和基於上下文的算術編碼、不支持場之外,編解碼器可以使用上文提到的主要先進技術,如幀內預測、4×4小變換、多參考幀、變尺寸的運動補償、環內濾波等,並且支持slice組;主檔則增加了對B slice、場、以及基於上下文的算術編碼技術的支持,高級檔還支持選擇使用8×8 DCT變換、以單幅圖像爲單位修正伸縮係數等特性。

4.3.6 Theora和VP3

4.3.6 VP8 & VP9

4.3.7 RealVideo

真正意義上的RealVideo技術應當始自RV30(RealVideo 8),包括之後發佈的RV40(RealVideo 9/10)。可以說,2003年H.264草案發布之前,RealVideo的壓縮技術一直處於領先地位,RV30和RV40的關鍵算法均參考自尚處於繁瑣的標準化過程中的H.26L,但由於沒有及時佔領市場(標準化組織在標準制定過程中也一直擔心這一點6)),等到H.264正式發佈,RealVideo基本踏上了死亡之路。在數字媒體產業的發展過程中,私有技術和標準之爭一直沒有停息,相對於標準來說,私有技術的成熟週期短,對市場的反應速度更加快捷,技術授權方面也更加簡單,標準則具備更強的兼容性,有利於產業鏈中各方廠商的協作,然而,由於標準往往是衆多企業利益妥協的結果,其中的專利權屬問題異常複雜,且從技術角度講,很容易變得臃腫不堪,增加實現難度。

RealVideo 9主要採用了以下類H.264的技術:

  1. 4×4矩陣整數變換
  2. 1/4像素運動預測
  3. 幀內預測
  4. 基於16×8、8×16以及8×8塊尺寸的運動預測
  5. 雙向預測
  6. 環內平滑濾波

其沒有采用的技術包括:

  1. 多參考幀技術
  2. 小於8×8塊尺寸的運動預測技術
  3. 小於16×16塊尺寸的雙向預測技術
  4. 算術編碼

這種折衷在某種程度上減輕了編解碼器的運算負擔,在H.264標準制定的初期是非常有意義的——因爲屆時尚沒有足夠強大的硬件來支持JVT龐大的運算模型,然而隨着時間的推移,硬件能力很快達到足以應付更復雜的運算需求的程度,此時RealVideo的優勢基本喪失。不過,RealVideo技術還有一個亮點,就是環內濾波。RealVideo的環內濾波較H.264更爲複雜,因此可以提供更好的主觀質量,但這一優勢更多體現在低比特率或低分辨率的場景,而隨着網絡帶寬的增加,高比特慮、高解晰度的視頻內容成爲主流,這個優勢也難以具備足夠的競爭力。

在之後的幾年中,Real一直致力於研究下一代視頻編碼技術(NGV),但其着眼點與JVT的HEVC並不同,NGV強調通過編碼與後處理的結合改善視頻的主觀質量,而HEVC充其量只能作爲H.264的一個增強版本。只是,由於計算複雜度遠遠超出了目前硬件的能力,NGV一直無法進入商用階段,最終以被轉讓給Intel而告終。

4.3.8 HEVC

2005年,MPEG組織開始着手探索進一步提高視頻壓縮效率的可行性,最終,多名專家達成一致,認爲通過採用已有的先進技術完全有可能令視頻壓縮效率繼續上升一個臺階。隨後,一個負責開發下一代視頻壓縮標準的聯合組織JCT-VC於2010年正式成立,2013年初,冠名爲HEVC的新標準發佈第一版草案,按照先前的習慣,這個標準也被稱爲H.265。HEVC的目標是在保持同等質量的前提下,將視頻數據的比特率降低爲H.264的二分之一,JCT-VC聲稱這一目標已經達到。

HEVC最大的亮點是利用四叉樹結構來組織編碼單位,從而使基於變尺寸塊的運動補償預測算法在性能上幾近達到其理論上的極致。

傳統以宏塊爲基本編碼組織單位可以達到的運動補償精度(以AVC爲例):

以最大可達64×64的四叉樹結構爲基本編碼組織單位可以達到的運動補償精度和變換精度:

編碼樹單元(CTU)和編碼樹塊(CTB)

CTU即上文提到的基本編碼組織單位,典型的場景中由一個亮度數據和兩個色差數據的四叉樹組成,即CTB。亮度CTB的大小可以在編碼前預先設定爲16、32或64,CTB越大,編碼性能越佳。

編碼塊(CB)和編碼單元(CU)

CB是CTB中的四叉樹的葉子節點,亮度CB和色差CB共同構成CU。亮度CB的最小尺寸可以在編碼前預先設定,不能小於8.

預測單元(PU)和預測塊(PB)

PB是運動預測時使用的塊,其劃分由CB給出,一個CB可以作爲一個PB,也可以劃分爲兩個或四個PB,第三種情況僅在CB達到允許的最小尺寸是出現。

變換單元(TU)和變換塊(TB)

TB在CB的基礎上樹狀分裂形成,允許的分裂深度可以在編碼前預先設定。HEVC定義了4×4、8×8、16×16以及32×32的整數變換矩陣,滿足不同TB的變換需求,允許使用的最大TB和最小TB也可以在編碼前預先設定。

4.4 語音

4.5 聲音

五、質量評估與改善

六、數字版權保護

七、軟件構架及解決方案

一個產品級的多媒體應用軟件——如媒體播放器、影視剪輯工作室、格式轉換工具以及視頻會議等——背後必然隱藏着一個高性能且具備極強擴展性軟件架構。這一架構需要針對各種不同用例提供便捷的應用編程接口,同時負責組織協調各個功能模塊的工作,這些功能模塊往往需要實現相當複雜的算法如媒體的編解碼和後處理等,因此自耦合性強、運算強度大,通常以庫或者插件的形式接入到架構中,由於通信協議和壓縮算法演進迅速,架構必須保證功能的可擴展性,以適應由新技術演進引起的應用軟件升級。

一個典型的多媒體軟件架構常常是這樣子的:

一個被稱爲框架的軟件中間層負責爲各種不同的應用提供統一的編程接口,從而降低應用軟件開發的難度,這個框架還負責以統一的接口接入不同形式的媒體處理功能模塊,並規範一個統一的運作策略使這些功能模塊能夠有效地協同工作。

7.1 DirectShow

7.2 QuickTime SDK

7.3 Helix DNA

7.4 OpenMAX

OpenMAX不是一個軟件架構或實現,它是Khronos組織制定的一組針對多媒體軟件系統的API標準。制定API標準在軟件產業界是比較常見的行爲,其目的一般是爲了增強軟件模塊的可移植性,但由於整個過程糅合了軟件商、芯片公司、OEM廠商等各方的利益,常常是無疾而終的下場,然而某些標準也會得到廣泛的應用,比如同樣是Khronos制定的OpenGL。OpenMAX的普及程度顯然遠不及OpenGL,但由於Android系統中採用了OpenMAX IL的接口去調用音視頻解碼器,各芯片廠商均會提供OpenMAX IL接口的支持,只是接口的行爲仍舊會因芯片的不同而不同,似乎也沒有完全達到當初制定該標準的初衷。

下圖來自Khronos官網,描繪了OpenMAX在制定者心目中的願景:

但目前應用比較多的只有OpenMAX IL接口,主要在Android中,而且是以獨立組件的形式被使用。

7.4.1 OpenMAX IL

OpenMAX IL接口實現了對媒體組件的封裝,編解碼器、打包模塊、解包模塊、後處理等等都屬於此類組件。定義的API分爲核心和組件兩部分,核心API負責系統的初始化、組件的管理、組件之間的通信等,組件API負責各具體組件的配置、運行等。在Khronos規範中,調用者、核心和組件的關係如下:

核心API主要包括:

  OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_Init(void);
  OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_Deinit(void);
 
  OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_ComponentNameEnum(
    OMX_OUT OMX_STRING cComponentName, 
    OMX_IN  OMX_U32 nNameLength, 
    OMX_IN  OMX_U32 nIndex);
  OMX_API OMX_ERRORTYPE OMX_GetComponentsOfRole(OMX_IN OMX_STRING role, OMX_INOUT OMX_U32 *pNumComps, OMX_INOUT OMX_U8  **compNames);
  OMX_API OMX_ERRORTYPE OMX_GetRolesOfComponent(OMX_IN OMX_STRING compName, OMX_INOUT OMX_U32 *pNumRoles, OMX_OUT OMX_U8 **roles); 
 
  OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_GetHandle(
    OMX_OUT OMX_HANDLETYPE* pHandle, 
    OMX_IN  OMX_STRING cComponentName,
    OMX_IN  OMX_PTR pAppData,
    OMX_IN  OMX_CALLBACKTYPE* pCallBacks);
  OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_FreeHandle(
    OMX_IN  OMX_HANDLETYPE hComponent);
 
  OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_SetupTunnel(
    OMX_IN  OMX_HANDLETYPE hOutput,
    OMX_IN  OMX_U32 nPortOutput,
    OMX_IN  OMX_HANDLETYPE hInput,
    OMX_IN  OMX_U32 nPortInput);

其中,OMX_Init()和OMX_Deinit()分別用於OpenMAX系統的初始化和退出。

OMX_GetHandle和OMX_FreeHandle分別用於OpenMAX組件的裝載和卸載。OpenMAX組件在系統中以名字爲標識,如:”OMX.Nvidia.h264.decode”,其中”Nvidia”表示芯片廠家,”h264.decode”表示Role。利用OMX_ComponentNameEnum遍歷可以獲取系統中所有可用的組件的名稱:

    OMX_ERRORTYPE Error = OMX_ErrorNone;
    OMX_STRING CompName = (OMX_STRING) malloc(OMX_MAX_STRINGNAME_SIZE);
    int i = 0;
    do 
    {
        Error = OMX_ComponentNameEnum(CompName, OMX_MAX_STRINGNAME_SIZE, i);
        if (Error == OMX_ErrorNone)
        {    
            printf("%s\n", CompName);
            i++;
        }
        else
            break;
    } while (1);

此外,通過OMX_GetComponentsOfRole和OMX_GetRolesOfComponent兩個函數還可以枚舉給定Role的所有組件的名稱以及給定名城的某個組件的所有的Role(有時一個組件不僅僅扮演一個Role)。

返回的OMX_HANDLETYPE標識裝載的組件,以此可調用組件API。實際上OMX_HANDLETYPE是一個OMX_COMPONENTTYPE結構,內部包含了實現組建接口的一系列函數指針。在裝載組件時,需要一個OMX_CALLBACKTYPE結構作爲輸入參數,用於響應該組件的所有事件,包括:普通事件、EmptyBufferDone事件和FillBufferDone事件。

typedef struct OMX_CALLBACKTYPE
{
   OMX_ERRORTYPE (*EventHandler)(
        OMX_IN OMX_HANDLETYPE hComponent,
        OMX_IN OMX_PTR pAppData,
        OMX_IN OMX_EVENTTYPE eEvent,
        OMX_IN OMX_U32 nData1,
        OMX_IN OMX_U32 nData2,
        OMX_IN OMX_PTR pEventData);
    OMX_ERRORTYPE (*EmptyBufferDone)(OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_PTR pAppData, OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
    OMX_ERRORTYPE (*FillBufferDone)(OMX_OUT OMX_HANDLETYPE hComponent, OMX_OUT OMX_PTR pAppData, OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);
} OMX_CALLBACKTYPE;

組件API包括:

OMX_ERRORTYPE (*GetComponentVersion)(
            OMX_IN  OMX_HANDLETYPE hComponent,
            OMX_OUT OMX_STRING pComponentName,
            OMX_OUT OMX_VERSIONTYPE* pComponentVersion,
            OMX_OUT OMX_VERSIONTYPE* pSpecVersion,
            OMX_OUT OMX_UUIDTYPE* pComponentUUID);
 
OMX_ERRORTYPE (*SendCommand)(
            OMX_IN  OMX_HANDLETYPE hComponent,
            OMX_IN  OMX_COMMANDTYPE Cmd,
            OMX_IN  OMX_U32 nParam1,
            OMX_IN  OMX_PTR pCmdData);   
 
OMX_ERRORTYPE (*GetParameter)(OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_INDEXTYPE nIndex, OMX_INOUT OMX_PTR pComponentParameterStruct);
OMX_ERRORTYPE (*SetParameter)(OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_INDEXTYPE nIndex, OMX_IN OMX_PTR pComponentParameterStruct);
OMX_ERRORTYPE (*GetConfig)(OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_INDEXTYPE nIndex, OMX_INOUT OMX_PTR pComponentConfigStruct);
OMX_ERRORTYPE (*SetConfig)(OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_INDEXTYPE nIndex, OMX_IN OMX_PTR pComponentConfigStruct);
OMX_ERRORTYPE (*GetExtensionIndex)(OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_STRING cParameterName, OMX_OUT OMX_INDEXTYPE* pIndexType);
 
OMX_ERRORTYPE (*GetState)( OMX_IN  OMX_HANDLETYPE hComponent, OMX_OUT OMX_STATETYPE* pState);
OMX_ERRORTYPE (*UseBuffer)(
            OMX_IN OMX_HANDLETYPE hComponent,
            OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
            OMX_IN OMX_U32 nPortIndex,
            OMX_IN OMX_PTR pAppPrivate,
            OMX_IN OMX_U32 nSizeBytes,
            OMX_IN OMX_U8* pBuffer);
OMX_ERRORTYPE (*AllocateBuffer)(
            OMX_IN OMX_HANDLETYPE hComponent,
            OMX_INOUT OMX_BUFFERHEADERTYPE** ppBuffer,
            OMX_IN OMX_U32 nPortIndex,
            OMX_IN OMX_PTR pAppPrivate,
            OMX_IN OMX_U32 nSizeBytes);
OMX_ERRORTYPE (*FreeBuffer)(OMX_IN  OMX_HANDLETYPE hComponent, OMX_IN OMX_U32 nPortIndex, OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
 
OMX_ERRORTYPE (*EmptyThisBuffer)(OMX_IN  OMX_HANDLETYPE hComponent, OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*FillThisBuffer)(OMX_IN  OMX_HANDLETYPE hComponent, OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
 
OMX_ERRORTYPE (*SetCallbacks)(OMX_IN  OMX_HANDLETYPE hComponent, OMX_IN OMX_CALLBACKTYPE* pCallbacks, OMX_IN OMX_PTR pAppData);

定義OpenMAX IL API的過程中,其設計者儘可能保證普適性,以使其滿足不同的芯片商、軟件提供商以及產品製造商的需求。最明顯的特徵包括:

  1. 通過回調函數的機制來完成輸入輸出,以保持對同步機制和異步機制的統一支持。
  2. 同時提供分配緩存和複用緩存的API,既支持調用者管理對緩存的管理,也支持組件對緩存的管理。
7.4.1.1 Port參數的查詢和設置

OpenMAX IL組件基於Port進行數據處理,承載數據的緩衝必須附着在給定的Port上。一個Port應具備如下幾個基本參數:

  1. Index:該Port在組件中的唯一索引號;
  2. Domain:該Port的作用域(音頻、視頻、圖像或其他)。
  3. Dir:該Port的數據傳輸方向(輸入、還是輸出)。

一個組件必須實現索引號爲0x1000002~0x1000005的參數接口,即OMX_IndexParamAudioInit/OMX_IndexParamImageInit/OMX_IndexParamVideoInit/OMX_IndexParamOtherInit,不同作用域的組件使用不同的索引號。通過調用GetParameter,OpenMAX IL的使用者可以獲取組件的Port數目和編號,這些信息存放在OMX_PORT_PARAM_TYPE結構中:

typedef struct OMX_PORT_PARAM_TYPE {
    OMX_U32 nSize;              /**< size of the structure in bytes */
    OMX_VERSIONTYPE nVersion;   /**< OMX specification version information */
    OMX_U32 nPorts;             /**< The number of ports for this component */
    OMX_U32 nStartPortNumber;   /** first port number for this type of port */
} OMX_PORT_PARAM_TYPE;

Port參數信息的查詢和配置則要通過索引號爲0x2000001(OMX_IndexParamPortDefinition)的參數接口實現,這個參數接口對應OMX_PARAM_PORTDEFINITIONTYPE結構:

typedef struct OMX_PARAM_PORTDEFINITIONTYPE {
    OMX_U32 nSize;                 /**< Size of the structure in bytes */
    OMX_VERSIONTYPE nVersion;      /**< OMX specification version information */
    OMX_U32 nPortIndex;            /**< Port number the structure applies to */
    OMX_DIRTYPE eDir;              /**< Direction (input or output) of this port */
    OMX_U32 nBufferCountActual;    /**< The actual number of buffers allocated on this port */
    OMX_U32 nBufferCountMin;       /**< The minimum number of buffers this port requires */
    OMX_U32 nBufferSize;           /**< Size, in bytes, for buffers to be used for this channel */
    OMX_BOOL bEnabled;             /**< Ports default to enabled and are enabled/disabled by
                                        OMX_CommandPortEnable/OMX_CommandPortDisable.
                                        When disabled a port is unpopulated. A disabled port
                                        is not populated with buffers on a transition to IDLE. */
    OMX_BOOL bPopulated;           /**< Port is populated with all of its buffers as indicated by
                                        nBufferCountActual. A disabled port is always unpopulated. 
                                        An enabled port is populated on a transition to OMX_StateIdle
                                        and unpopulated on a transition to loaded. */
    OMX_PORTDOMAINTYPE eDomain;    /**< Domain of the port. Determines the contents of metadata below. */
    union {
        OMX_AUDIO_PORTDEFINITIONTYPE audio;
        OMX_VIDEO_PORTDEFINITIONTYPE video;
        OMX_IMAGE_PORTDEFINITIONTYPE image;
        OMX_OTHER_PORTDEFINITIONTYPE other;
    } format;
    OMX_BOOL bBuffersContiguous;
    OMX_U32 nBufferAlignment;
} OMX_PARAM_PORTDEFINITIONTYPE;

前面提到的三個基本參數由nPortIndex、eDir和eDomain給出,與Port相關的緩衝參數由nBufferCountMin,nBufferSize及nBufferCountActual給出,分別表示Port需要的最少的緩衝個數、最小的緩存尺寸以及實際的緩衝個數,其中,只有第三個參數可寫,此外,還有兩個只讀的參數bBuffersContiguous和nBufferAlignment,給出緩衝區是否需要連續內存以及其對齊方式。bEnabled和bPopulated是兩個只讀的狀態標記,分別表示Port是否使能以及Port指定的附屬緩衝區是否全部到位。

format則是與作用域相關的具體的參數設定。對於音頻,相應的結構爲OMX_AUDIO_PORTDEFINITIONTYPE:

typedef struct OMX_AUDIO_PORTDEFINITIONTYPE {
    OMX_STRING cMIMEType;            
    OMX_NATIVE_DEVICETYPE pNativeRender; 
    OMX_BOOL bFlagErrorConcealment; 
    OMX_AUDIO_CODINGTYPE eEncoding; 
} OMX_AUDIO_PORTDEFINITIONTYPE;

對於單一Role的音頻組件,其輸入Port和輸出Port的cMIMEType、eEncoding通常是缺省的。

視頻對應的結構爲OMX_VIDEO_PORTDEFINITIONTYPE:

typedef struct OMX_VIDEO_PORTDEFINITIONTYPE {
    OMX_STRING cMIMEType;
    OMX_NATIVE_DEVICETYPE pNativeRender;
    OMX_U32 nFrameWidth;
    OMX_U32 nFrameHeight;
    OMX_S32 nStride;
    OMX_U32 nSliceHeight;
    OMX_U32 nBitrate;
    OMX_U32 xFramerate;
    OMX_BOOL bFlagErrorConcealment;
    OMX_VIDEO_CODINGTYPE eCompressionFormat;
    OMX_COLOR_FORMATTYPE eColorFormat;
    OMX_NATIVE_WINDOWTYPE pNativeWindow;
} OMX_VIDEO_PORTDEFINITIONTYPE;

視頻組件具備一些特定的參數。其中,nFrameWidth和nFrameHeight表示以像素爲單位的圖像的寬度和高度,對於輸入Port,如果這兩個值設置爲0,組件會對圖像的尺寸進行自動檢測;nStride和nSliceHeight則是描述非壓縮圖像的緩衝區的參數,nStride表示圖像緩衝區的橫跨字節數,可讀可寫;nSliceHeight則表示圖像緩衝區的縱向高度,只讀。nBitrate表示壓縮數據的比特率,0爲變比特率或未知比特率;xFramerate表示非壓縮數據的幀率,0爲變幀率或未知幀率。eCompressionFormat表示壓縮格式,如果eCompressionFormat設置爲OMX_VIDEO_CodingUnused,則eColorFormat表示非壓縮數據的格式。對於視頻編碼器組件,輸入Port的eCompressionFormat可設爲OMX_VIDEO_CodingUnused,同時指定eColorFormat,而輸出Port的eCompressionFormat需要設置爲目的格式,同時還要設置編解碼器參數,對於視頻解碼器組件,輸入Port的eCompressionFormat可設可不設,組件會對輸入的碼流進行自動檢測,輸出Port的eCompressionFormat可設爲OMX_VIDEO_CodingUnused,如果不打算使用缺省的輸出格式,還需要同時指定eColorFormat。

圖像組件對應的結構爲OMX_IMAGE_PORTDEFINITIONTYPE,其成員的含義大致與OMX_VIDEO_PORTDEFINITIONTYPE相同:

typedef struct OMX_IMAGE_PORTDEFINITIONTYPE {
    OMX_STRING cMIMEType;
    OMX_NATIVE_DEVICETYPE pNativeRender;
    OMX_U32 nFrameWidth; 
    OMX_U32 nFrameHeight;
    OMX_S32 nStride;     
    OMX_U32 nSliceHeight;
    OMX_BOOL bFlagErrorConcealment;
    OMX_IMAGE_CODINGTYPE eCompressionFormat;
    OMX_COLOR_FORMATTYPE eColorFormat;
    OMX_NATIVE_WINDOWTYPE pNativeWindow;
} OMX_IMAGE_PORTDEFINITIONTYPE;

對於非單一Role的組件,可以使用索引號爲0x4000001/0x5000001/0x6000001(OMX_IndexParamAudioPortFormat/OMX_IndexParamImagePortFormat/OMX_IndexParamVideoPortFormat)的參數來查詢某個Port支持的數據格式。以視頻爲例,參數索引號爲0x4000001(OMX_IndexParamAudioPortFormat),對應的結構爲OMX_AUDIO_PARAM_PORTFORMATTYPE:

typedef struct OMX_AUDIO_PARAM_PORTFORMATTYPE {
    OMX_U32 nSize;                  
    OMX_VERSIONTYPE nVersion;      
    OMX_U32 nPortIndex;             
    OMX_U32 nIndex;                 
    OMX_AUDIO_CODINGTYPE eEncoding; 
} OMX_AUDIO_PARAM_PORTFORMATTYPE;

查詢時,需要設置除eEncoding之外的其他域,組件返回不同nIndex對應的eEncoding。代碼示例如下:

    OMX_AUDIO_PARAM_PORTFORMATTYPE AudioFormat;
    CONFIG_SIZE_AND_VERSION(AudioFormat);
    AudioFormat.nPortIndex = in;
    for (i = 0;;i++)
    {
        AudioFormat.nIndex = i;
 
        error = OMX_GetParameter(m_Handle, OMX_IndexParamAudioPortFormat, &AudioFormat);
        if (error != OMX_ErrorNoMore)
        {
            continue;
        }
 
        if (AudioFormatType == AudioFormat.eEncoding)
        {
            LOGI("Audio Format Found on Input Port, with Index=%d\n", i);
            break;
        }
    }
7.4.1.2 組件狀態的轉換及緩衝管理

組件通過OMX_SendCommand發送OMX_CommandStateSet的命令發起狀態轉換,轉換的結果由OMX_EventCmdComplete事件進行通知,正常的狀態轉換序列爲,:

LOADED <-> IDLE <-> EXECUTING <-> PAUSED

其中,LOADED爲組件的初始狀態。 在向IDLE狀態轉換的命令發起之後,組件各Port所需的緩衝需要得到分配,成功的話纔會進入IDLE狀態。此時數據處理過程尚不能啓動,需要進一步發起向EXECUTING狀態的轉換,轉換成功後,纔可以調用OMX_EmptyThisBuffer和OMX_FillThisBuffer請求數據處理。 如果各Port所需的緩衝資源分配失敗,組件會進入一個暫時的WAIT_FOR_RESOURCE狀態。

若組件遭遇異常,則進入INVALID狀態,此時需要重新裝載組件。

關聯於某Port的緩衝的管理藉助數據結構OMX_BUFFERHEADERTYPE實現:

typedef struct OMX_BUFFERHEADERTYPE
{
    OMX_U32 nSize;             
    OMX_VERSIONTYPE nVersion;  
    OMX_U8* pBuffer;            
    OMX_U32 nAllocLen;          
    OMX_U32 nFilledLen;         
    OMX_U32 nOffset;           
    OMX_PTR pAppPrivate;       
    OMX_PTR pPlatformPrivate;   
    OMX_PTR pInputPortPrivate;  
    OMX_PTR pOutputPortPrivate; 
    OMX_HANDLETYPE hMarkTargetComponent; 
    OMX_PTR pMarkData;          
    OMX_U32 nTickCount;         
    OMX_TICKS nTimeStamp;          
    OMX_U32 nFlags;           
    OMX_U32 nOutputPortIndex;     
    OMX_U32 nInputPortIndex;      
}   OMX_BUFFERHEADERTYPE;

相關的函數包括:

    OMX_ERRORTYPE (*AllocateBuffer)(
            OMX_IN OMX_HANDLETYPE hComponent,
            OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
            OMX_IN OMX_U32 nPortIndex,
            OMX_IN OMX_PTR pAppPrivate,
            OMX_IN OMX_U32 nSizeBytes);
    OMX_ERRORTYPE (*UseBuffer)(
            OMX_IN OMX_HANDLETYPE hComponent,
            OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
            OMX_IN OMX_U32 nPortIndex,
            OMX_IN OMX_PTR pAppPrivate,
            OMX_IN OMX_U32 nSizeBytes,
            OMX_IN OMX_U8* pBuffer);            
    OMX_ERRORTYPE (*FreeBuffer)(
            OMX_IN  OMX_HANDLETYPE hComponent,
            OMX_IN  OMX_U32 nPortIndex,
            OMX_IN  OMX_BUFFERHEADERTYPE* pBuffer);
    OMX_ERRORTYPE (*EmptyThisBuffer)(
            OMX_IN  OMX_HANDLETYPE hComponent,
            OMX_IN  OMX_BUFFERHEADERTYPE* pBuffer);
    OMX_ERRORTYPE (*FillThisBuffer)(
            OMX_IN  OMX_HANDLETYPE hComponent,
            OMX_IN  OMX_BUFFERHEADERTYPE* pBuffer);

AllocateBuffer分配緩衝及管理緩衝的OMX_BUFFERHEADERTYPE結構,返回給ppBufferHdr,其中的數據域nOutputPortIndex、nInputPortIndex根據輸入參數nPortIndex初始化,pAppPrivate、pInputPortPrivate以及pOutputPortPrivate由輸入參數pAppPrivate初始化。該函數的調用時機爲:組件處於LOADED狀態且向IDLE狀態轉換的命令以及下達;或因資源分配失敗致使組件進入WAIT_FOR_RESOURCE狀態。如果組件其它狀態,需要將該組件禁止之後才能調用此函數。

典型的調用方式如下:

/* IL client asks component to allocate buffers */
for (i=0;i<pClient->nBufferCount;i++)
{
    OMX_AllocateBuffer(hComp, &pClient->pBufferHdr[i], pClient->nPortIndex, pClient, pClient->nBufferSize);
}

UseBuffer允許組件使用已經由輸入參數pBuffer指定的內存,該函數將創建OMX_BUFFERHEADERTYPE結構返回給ppBufferHdr,並採用和AllocateBuffer相同的方式初始化其中的某些數據域,UseBuffer也與AllocateBuffer相同。

典型的調用方式如下:

/* supplier port allocates buffers and pass them to non-supplier */
for (i=0;i<pPort->nBufferCount;i++)
{
    pPort->pBuffer[i] = malloc(pPort->nBufferSize);
    OMX_UseBuffer(pPort->hTunnelComponent, &pPort->pBufferHdr[i], pPort->nTunnelPort, pPort, pPort->nBufferSize, pPort->pBuffer[j]);
}

無論是使用UseBuffer還是AllocateBuffer,得到的OMX_BUFFERHEADERTYPE結構均需要由FreeBuffer釋放;對於後者,FreeBuffer還將同時釋放實際的緩衝區。

當組件進入EXECUTING狀態之後,其運作由EmptyThisBuffer和FillThisBuffer來驅動。這是兩個異步函數,輸入參數均爲OMX_BUFFERHEADERTYPE結構,EmptyThisBuffer作用於輸入Port,請求組件讀數據;FillThisBuffer作用於輸出Port,請求組件寫數據。調用EmptyThisBuffer時,需要在OMX_BUFFERHEADERTYPE結構的nFilledLen字段中給出輸入數據的長度。

這兩個函數均爲非阻塞函數,操作的完成由回調函數來通知:

    OMX_ERRORTYPE (*EmptyBufferDone)(OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_PTR pAppData, OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
    OMX_ERRORTYPE (*FillBufferDone)(OMX_OUT OMX_HANDLETYPE hComponent, OMX_OUT OMX_PTR pAppData, OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);

pAppData指向的正是調用OMX_GetHandle時嵌入的cookie,pBuffer則是處理中的OMX_BUFFERHEADERTYPE結構。

EmptyBufferDone事件產生於組件從輸入Port附着的緩衝區中成功讀取數據之後;FillBufferDone事件則產生於組件向輸出Port附着的緩衝區成功寫入數據之後。對於後者,OMX_BUFFERHEADERTYPE結構的字段nOffset和nFilledLen給出數據在緩衝中的偏移和長度。

如果緩衝區內存儲的是音視頻壓縮數據(如解碼器的輸入緩衝),需要支持三種存儲方式:

  1. 允許音視頻幀的分割和分組
  2. 允許音視頻幀的分組,但不允許分割
  3. 每個緩衝區僅允許存儲一個壓縮音視頻幀

對於第一種方式,要求解碼組件進行分幀和組幀,因此對組件的能力要求高,同時需要組件提供額外的幀緩衝,組件外部的控制邏輯也比較複雜;對於第二種方式,要求組件具備分幀能力。而實際上,流行的媒體容器如MOV、MKV等具備很好的組幀和分幀能力,在這種情況下采用第三種方式是可行的,而且能夠避免額外的運算量和存儲空間,提高性能。

通過nFlags字段可以設置緩衝的附加信息:

  • OMX_BUFFERFLAG_EOS:流結束附,當緩衝中包含的數據是媒體流的最後一部分時設置
  • OMX_BUFFERFLAG_STARTTIME:
  • OMX_BUFFERFLAG_DECODEONLY:
  • OMX_BUFFERFLAG_DATACORRUPT:
  • OMX_BUFFERFLAG_ENDOFFRAME:暗示緩衝中包含一個完整媒體幀的結束,之後無其餘媒體數據。
  • OMX_BUFFERFLAG_SYNCFRAME:關鍵幀標記
  • OMX_BUFFERFLAG_EXTRADATA:extra data標記,表示緩衝中的媒體數據後含有以OMX_OTHER_EXTRADATATYPE結構存放的附加數據。
  • OMX_BUFFERFLAG_CODECCONFIG:表示緩衝中的數據是配置數據,如H.264壓縮數據的SPS, PPS等。注意,OpenMAX IL不允許配置數據和媒體數據混在同一個緩衝中傳送。

nTimeStamp字段則標識了緩衝中的媒體數據的播放時間,單位微秒。

無論是nFlag,還是nTimeStamp,都是作用於緩衝區中的第一個起始於該緩衝區的媒體幀。

OMX_CommandMarkBuffer命令實現緩衝標記的功能。如果某個緩衝被標記,即使該緩衝中的數據經多個組件處理之後,仍然可以被發現。OMX_CommandMarkBuffer命令發送給某個Port,則該Port在接下來拿到第一個緩衝時對該緩衝進行標記,標記的信息來源於SendCommand的第二個輸入參數,這是一個結構體:

typedef struct OMX_MARKTYPE
{
    OMX_HANDLETYPE hMarkTargetComponent;   
    OMX_PTR pMarkData;   
} OMX_MARKTYPE;

hMarkTargetComponent表示需要對標記檢測的組件的句柄,pMarkData則是設置的標記信息,這兩個參數會被設入對應OMX_BUFFERHEADERTYPE結構的hMarkTargetComponent字段和pMarkData字段。如果某個組件發現自己的句柄正是OMX_BUFFERHEADERTYPE結構中的hMarkTargetComponent,則產生OMX_EventMark事件,註冊的EventHandler被調用。

7.4.1.3 OMX_EventPortSettingsChanged事件

組件Port的參數可能會在運行過程中發生變化,這種變化由OMX_EventPortSettingsChanged事件來通知。在收到Port參數變化的事件之後,需要檢查新的Port參數,並決定是否重新分配緩衝區。通常的處理流程爲:

獲取OMX_IndexParamPortDefinition參數 → 發送OMX_CommandFlush命令清空該Port → 發送OMX_CommandPortDisable命令禁止該端口 → 釋放已有的緩衝區並重新分配 → 發送OMX_CommandPortEnable命令使能該端口

7.4.1.4 回調函數設計

7.5 FFMPEG

FFMPEG是一個貨真價實的多媒體軟件解決方案,源碼開放,遵循LPGL,提供基於通用CPU的最優化的協議解析,格式解析及媒體編解碼能力,併兼容某些硬件API,支持常見的音視頻設備,爲目前絕大多數多媒體播放器合法或者不合法地使用着。其詳情請參見深入淺出FFMPEG

7.6 Android系統的多媒體結構

作爲一個專門針對移動設備的智能系統,其數字媒體的處理能力較之前的傳統手機操作系統要強大很多,在如智能電視和平板電腦等產品中甚至扮演着核心角色。

7.6.1 Android系統的多媒體能力

Android系統完整的應用開發接口是以Java的形式給出的,其媒體能力封裝到一個名爲android.media的包中,包含了大量實現數字影音功能的類以及相關的接口定義。其中,兩個最基礎的類MediaPlayer和MediaRecorder分別實現了數字媒體內容播放和錄製的功能。

7.7 GStreamer

八、系統部署

九、未來展望

2) ISO/IEC, Information technology — Generic coding of moving pictures and associated audio information: Systems, 2nd Edition, 2000
3) C. E. Shannon: A mathematical theory of communication. Bell System Technical Journal, vol. 27, pp. 379–423 and 623–656, July and October, 1948
4) Robert M.Gray and David L.Neuhoff, “Quantization”, IEEE Trans Information Theory, Vol.IT-44, Oct.1998: pp2325-2383
5) JAIN, J.R., and JAIN, A.K.: ‘Displacement measurement and its application in interframe coding’, I E E E Trans., COM-29, (12), 1981, pp. 179%1808
6) Iain E. Richardson, “The H.264 Advanced Video Compression Standard, 2nd Edition”, Wiley, July 27 2010,

原文地址 :http://3xin2yi.info/wwwroot/tech/doku.php/tech:multimedia:digital_media
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章