並行算法類型可以分為兩類
- Function-level Decomposition,按照功能模塊進行並行
- Data-level Decomposition,按照數據划分進行並行
Function-level Decomposition
在h.264解碼時進行功能划分,例如對於四核系統,各個核心分別執行下列任務
- 熵解碼framen
- 逆量化、逆變換framen-1
- 預測處理framen-2
- 去塊濾波framen-3
這種並行類型就是流水線類型,但這種類型在h.264解碼中會出現以下問題
- 各個功能部分耗費的時長不同,這依賴於實際的碼流數據,處理數據的吞吐量取決於其中最耗時的部分。如果僅僅是吞吐量的問題還能采取緩沖的方式來進行流水線優化(一旦一個核心完成了一幀的任務后,對任務成果進行緩存,如果下一幀的上一步驟已經完成,便立刻開始下一幀處理),不過問題是在預測處理中,inter宏塊部分需要進行運動補償,也就是需要依賴到其他幀,這意味着需要等待它所依賴的幀完全解碼完成后才能開始當前幀的處理,這就使得這種流水線類型的並行方法復雜多了。
- 並行度的擴展是有限的,這種並行類型的並行度依賴於功能模塊可以分成多少部分,例如上面的這個例子就分成四個功能部分,如此一來就只能分發給四個核心進行處理。
由於有上述的這些缺點,一般在h.264並行解碼器實現中都不會采用這種實現方式。
Data-level Decomposition
按照數據划分,在每部分進行划分並且處理后,還需要進行合並。在h.264中有多個級別的數據划分,這里挑出其中三個重點分析
1. Frame-level Parallelism
h.264分為I、P、B幀,其中I、p被用作參考幀,B幀常被用作非參考幀。而並行算法,對於完全不相關的數據才能並行處理,即對作為非參考幀的B幀才能平行處理。如果采用這種並行算法,需要有一個核來解析碼流,判斷幀的類型並且把幀分配給不同的核進行處理。
它的缺點如下:
- 有限擴展。一般來說P幀間的B幀都會比較少,因此並行度不高。
- 在h.264中,B幀是可以作為參考幀的,在這種情況下,這種Frame-level的並行算法就會無法進行了。有一個解決方法就是在編碼時規定B幀不作為參考幀(實際上x264在開啟了並行編碼時就不把B幀作為參考幀),但是這種編碼方式會導致碼流增加,並且我們這里討論的是解碼器,解碼器與編碼器是完全分開的,如果作為通用的h.264解碼器就應該完全支持標准中的特性。
出於上述原因,一般在並行解碼器實現中都不會采用這種實現方式。
2. Slice-level Parallelism
在h.264中,與其他高級視頻編碼標准一樣,每一幀都能分成一個或者多個slice。
slice的目的是為了增強傳輸出錯時的魯棒性,一旦傳輸出現錯誤,沒有出錯的slice並不會受到影響,這樣的話視頻的顯示質量的下降就會比較有限。一幀的各個slice間是相互獨立的,也就是說在熵解碼,預測等各種解碼操作時,slice間並不相互依賴。有了數據上的獨立,就能對他們進行並行編碼了。
它的缺點如下:
- 一幀中的slice數目有編碼器決定,一般不會太多。網上的h.264視頻大多數都是一幀只有一個slice,這就導致slice-level的並行算法失去效果了。
- 盡管slice間是相互獨立的,但是在去塊濾波(deblocking,在編碼時是可選的)時,是能超越slice邊界的,並且去塊濾波必須按照正常的視頻序列順序進行,這會降低slice-level並行算法的速度。
- 使用多slice編碼方式的主要缺點就是視頻碼率會增大。在前面第一點時已經提到,一般都是一幀只有一個slice,如果一幀中的slice數目增多,明顯的影響就是slice邊界增多,在進行宏塊的intra,inter預測時是不能跨過slice邊界的。另外,slice數目增多意味着會有更多的slice_header與起始碼。
下圖展示了1080p分辨率下4個不同視頻的slice數目與碼流增加的關系。在4個slice時,碼率增加不到5%,這在高碼率的應用中,如藍光播放中是可以接受的。但是更多的slice就會帶來更大的碼流,要記住,視頻壓縮的主要目的是壓縮,而不是並行處理,而且多slice的情況下,如果不采用去塊濾波會使得視頻質量下降。
由於上述的分析,一般在並行解碼器實現中不會采用這種實現方式。
3. Macroblock-level Parallelism
首先,宏塊(Macroblock/MB)並不是完全獨立的,為了能夠進行並行解碼,我們需要分析宏塊之間的依賴關系,找出相互獨立的宏塊,以此來判斷應該怎么並行。
其中可以分為兩大類MB-level的並行算法
- 2D-Wave
- 3D-Wave
2D-Wave
2D-Wave算法原理
2D-Wave就是把一幀內的宏塊進行並行處理的算法。為了實行這種算法,我們必須考慮一幀內宏塊間的依賴關系。
在h.264中,在宏塊的intra預測、inter預測、deblocking等多個功能部分都會依賴到特定的相鄰宏塊,所依賴的宏塊見下圖
在單一線程中處理一幀時,對宏塊處理的順序是從左到右,從上到下。按照這種順序,宏塊的依賴在解碼時,它依賴的宏塊都是可用的。而並行算法要求並行處理的數據相互獨立,那么相互獨立的宏塊出現在如下位置(knight-jump-diagonal)
在並行處理中,與一個宏塊相互獨立的最接近的宏塊位於(右2,上1)的相對位置上,因此他們能夠同時被處理。如下圖,完整的是已被處理過的宏塊,花格子的為正在被處理的宏塊,空白的為未被處理的宏塊。
這種算法被稱為2D-Wave。
與frame-leve,slice-leve並行算法不同的是,2D-Wave有着良好的可擴展性,因為它最大的並行度,即可以同時處理的宏塊數是依賴於幀的寬高
$ParMB_{max,2D} = min\left( \left \lceil mb\_width / 2 , mb\_height \right \rceil \right)$
如1080p的幀的宏塊數目為($120 \times 68 \ MBs$),它的最大並行度為$120/2 = 60$。
2D-Wave的缺點
- 在一幀處理的開始與末尾,並行度較低,如果是有多核的情況,那么在開始與結尾處就會有很多核出於空閑狀態。
- 在熵編碼為CABAC的情況,slice中每個宏塊的熵解碼都與它前一個宏塊相關,也就是每個宏塊都是相關的,那么,只能在熵解碼完成后才能開始進行並行重建宏塊。
2D-Wave的效率
我們前面討論了2D-Wave中並行的方法,當前正在重建的宏塊需要與相鄰行正在進行重建的宏塊相隔兩個宏塊,因此,並行解碼可以得到以下DAG(Directed Acyclic Graph)。
上圖是一個5x5宏塊幀的DAG,橫向為時間,縱向為幀內的每一行,每個結點代表一個宏塊的解碼,在這里我們假設每個宏塊的解碼時間相同,並忽略通信和同步所占用的時間。
DAG的深度(depth),即從開始到末尾的結點數目,也就是解碼一幀所需要的時間,記為$T_{\infty}$。DAG的總結點數,也就是采用非並行解碼時解碼一幀所需要的時間,記為$T_{s}$。通過這兩個理想狀態下的數據,我們可以得到采用2D-Wave並行算法進行解碼時的最大速度提升。
$SpeedUp_{max,2D} = \frac{T_s}{T_{\infty} }= \frac{mb\_width \times mb\_height}{mb\_width + 2\times(mb\_height - 1)}$
其中在解碼過程中最大的並行度為
$ParMB_{max,2D} = min\left( \left \lceil mb\_width / 2 , mb\_height \right \rceil \right)$
按照上面的結論,可以算出常見分辨率視頻在采用2D-Wave並行算法后的各項參數如下
Resolution name | Resolution (pixels) | Resolution (MBs) | Total MBs | Max.Speedup | Parallel MBs |
UHD,4320p | 7680x4320 | 480x270 | 129,600 | 127 | 240 |
QHD,2160p | 3840x2160 | 240x135 | 32,400 | 63.8 | 120 |
FHD,1080p | 1920x1080 | 120x68 | 8,160 | 32.1 | 60 |
HD,720p | 1280x720 | 80x45 | 3,600 | 21.4 | 40 |
SD,576p | 720x576 | 45x36 | 1,620 | 14.1 | 23 |
並行度變化曲線如下
橫軸為時間,以宏塊為單位,0為第(0,0)個宏塊的解碼結點;縱軸為並行度。可以看到對於一幀的處理,並行度先增高后降低,平均並行度約為最大並行度的一半。
當然,上述分析都是基於“宏塊解碼時間相同”這一理想情況的。實際上,由於宏塊類型不同等各種原因會使得宏塊的解碼時間不固定,這會需要同步機制來調整宏塊的解碼順序,也就帶來了相應的開銷,因此無法達到理論上的最大速度提升。
為了分析2D-Wave在實際解碼中的速度提升,我們可以在實際的解碼器中進行模擬實驗。如在ffmpeg解碼器上,在解碼一幀的時候,我們記錄這一幀每一個宏塊解碼所需要的時間。結合這些時間數據與上述分析(宏塊需要等待它依賴的宏塊解碼完成后才能開始解碼),我們就能拼湊出實際解碼一幀的DAG,因此可以得到2D-Wave在實際應用中的速度提升。
不同視頻的速度提升見下表
Input Video | Blue_sky | Pedestrian_area | Riverbed | Rush_hour |
Max.Speedup | 19.2 | 21.9 | 24.0 | 22.2 |
以上視頻均為1080p,理論上的速度提升為32.1,實際上平均下降了33%。
3D-Wave
2D-Wave已經可得到足夠高的速度提升,但是如果在如100核的這種多核處理器環境下解碼,2D-Wave就顯得不夠適用了。下面將討論3D-Wave算法,這種算法就能很好地適用於多核,甚至超多核的解碼環境。
3D-Wave算法基於一個現象:連續的幀之間通常不會存在超快速的運動,運動向量(MV)一般來說都會比較小。這就表明了在解碼當前幀的某個宏塊時,不需要等待前一幀完全解碼完成,只要在當前宏塊所依賴的參考宏塊所在的區域重建完成后即可開始當前宏塊的解碼。當然,這種算法在同一幀上還是有與2D-Wave相同的約束條件,3D-Wave可以看作是2D-Wave的升級版。
3D-Wave算法分為兩種實現方式
1. Static 3D-Wave
h.264標准中定義了MV的最大長度(參見附件表A-1,請注意區別於最大搜索范圍)。當視頻為1080p時,MV最大長度為512像素。Static 3D-Wave算法就是統一以這個MV的最大值來作為當前解碼宏塊的依賴,也就是說只有當參考幀中的這個MV所覆蓋的區域內的所有宏塊全部完成重建后,才能開始當前宏塊的重建。
為了分析Static 3D-Wave算法,我們需要像分析2D-Wave時一樣,假設每個宏塊的解碼時長都是一樣的,並且假設該算法在解碼時出現以下情形。
- B幀可以作為參考幀。這意味着任何幀都能作為參考幀。
- 參考幀永遠為視頻序列的上一幀。這意味着只有等上一幀相關區域的宏塊解碼完畢后才能開始當前宏塊解碼,如此一來當前宏塊開始解碼的時間就會比參考幀不是上一幀的情況更加晚。
- 只有第一幀為I幀,並且其他幀中不會出現intra宏塊。這意味着除了第一幀之外,其他幀的宏塊在解碼是都有幀間依賴,也就是說需要等待它所依賴的參考幀上的相關宏塊解碼完成后才能開始當前宏塊的解碼。
上述情形對Static 3D-Wave算法而言是最壞的情況,基於這些假設,我們下面計算Static 3D-Wave的並行度。
在最大MV為16的情況下,如果當前解碼的宏塊為第二幀的宏塊(0,0),那么在第一幀中,最大MV所包含的宏塊為(0,0),(0,1),(1,0),(1,1)。不過由於需要進行子像素重建,重建插值會另外多用到兩個宏塊(1,2),(2,1)。因此第二幀的宏塊(0,0)所依賴的第一幀的宏塊為(0,0),(0,1),(1,0),(1,1),(1,2),(2,1),根據這個結果,我們可以知道在解碼完第一幀的(2,1)之后即可開始第二幀(0,0)的解碼。
最大MV為16的情況下第一幀的(0,0)與第二幀的(0,0)宏塊解碼時間相差6個單位。按照同樣的分析方法,我們可以得到最大MV為32,64,128時,幀間延遲分別為9,15,27個單位。規律算式如下
$3 \times [n/16]+3$
只要保證了這個幀間延遲,后續的宏塊解碼按照2D-Wave的方式進行解碼即可得到最大的並行度。
依據上述分析,1080p在不同最大MV時的並行度如下所示
2. Dynamic 3D-Wave
與Static 3D-Wave中采用最長MV不同,Dynamic 3D-Wave算法采用的是宏塊中實際的MV,因此依賴的是實際MV對應的參考幀區域。如此一來我們就無法通過單純的分析來得到該算法的並行度了。
Dynamic 3D-Wave的並行度分析需要基於實際碼流,通過跟蹤碼流中每個宏塊所在的位置以及每個宏塊的MV就可以得到各宏塊之間的依賴關系。在假設每個宏塊解碼時間相同的前提下,我們依據宏塊間的這種依賴關系就能模擬出Dynamic 3D-Wave的並行度。
模擬方法如下:
在比如ffmpeg這類的解碼器中進行解碼時,為每個宏塊進行賦值。第一幀的第一個宏塊賦值為1,我們可以看做第一幀的宏塊(0,0)的解碼時間戳為1。在以后每解碼一個宏塊,我們都能基於上述的依賴分析得到被解碼宏塊的依賴宏塊,而依賴宏塊中最后解碼的宏塊的解碼時間戳加1,就是當前宏塊的解碼時間戳。得到宏塊的解碼時間戳后,就能統計出在各個時間戳上同時解碼宏塊的數目,即並行度。
下面圖顯示了各個視頻在解碼過程中的並行度變化。
其中可以看出
- 如Blue_sky,可以有高達7000的並行度,比Static 3D-Wave(Max MV=16)的2000並行度高得多。
- 並行度依賴於具體的視頻情況。如Pedestrian_area中由於有快速運動的物體,這會導致出現較大的MV,從而降低並行度。
- 由於測試視頻都只有400幀,因此我們看到並行度呈山峰形狀。如果視頻足夠長的話,就可以看到在高並行度處更為平緩的曲線。
在Dynamic 3D-Wave算法的實際解碼過程中,也會碰到諸如通信,同步等甚至比2D-Wave更多的各種制約,效率會因此下降。較為合理的假設是,這些制約會導致約為50%的效率下降,高於2D-Wave的33%。
Other Data-level Parallelism
1. GOP-level Parallelism
各個GOP(Group Of Picture)之間是相互獨立的,因此能以GOP為並行處理的數據單元,但是這一級的並行也會受到以下的限制
- 每個GOP需要維護自己的參考圖像列表(這種情況在上述3D-Wave中也可能會出現),參考圖像列表里面的內容都是無壓縮的YUV圖像,一旦並行度升高后,維護參考圖像列表所需要的內存也是相當多的。
- GOP的組成方式是在編碼前就由編碼器定義的,因此會出現各種各樣的GOP形式,甚至可能出現一整個視頻序列只有一個GOP的情況,這很大程度限制了GOP算法的並行度
2. sub-block level Parallelism
這種並行算法是在Macroblock-level 3D-Wave的基礎上,把macroblock分成更小的sub-block,除此之外並沒有什么不同。兩者都是通過當前解碼的MB/sub-block的位置與MV找出其所依賴的區域,當所依賴的區域解碼完成后就可以開始當前MB/sub-block的解碼。然而以解碼sub-block作為一個解碼任務來說實在太小了,在3.3G的Inter Sandybridge處理器上解碼一個宏塊平均只需要2μs,而且考慮到生成一個新的解碼任務也需要耗費時間,sub-block level的並行算法就顯得不是很有必要了。
關於h.264並行解碼算法的更詳細分析請參考Ben Juurlink · Mauricio Alvarez-Mesa · Chi Ching Chi · Arnaldo Azevedo · Cor Meenderinck · Alex Ramirez:《Scalable Parallel Programming Applied to H.264/AVC Decoding》