以下通過剖析一些經(jīng)驗來了解視頻解碼優(yōu)化
1
在嵌入式系統(tǒng)中實現(xiàn)MPEG4的視頻解碼
有兩種方法可行
(1)采用ffmpeg(mplayer 的核心就是采用ffmpeg),然后對ffmpeg
mp4解碼優(yōu)化
1)對IDCT匯編化,并優(yōu)化VLD的實現(xiàn) ->inline&匯編化
2)根據(jù)ARM9 cache & cache
line的大小做MB的分組,使得每次可以同時處理多個MB
即 對多個MB在一個循環(huán)內(nèi)做VLD--->IDCT-->MC--.......
->耦合
3)優(yōu)化關(guān)鍵代碼段的內(nèi)存訪問(MC)
->inline&匯編化
4)不要使用FFmpeg內(nèi)置的img_convert()做yuv2rgb轉(zhuǎn)換
->inline&匯編化
5)對解碼庫做ARM指令集優(yōu)化 ->體系結(jié)構(gòu)優(yōu)化
configured ffmpeg
with cpu = ARMV4L would give you a better performance
If you have IPP,you
can enable it, you can obtain huge enhancement
IPP="Intel"? Integrated
Performance Primitives intel高性能構(gòu)件庫 (only for xScale)
(2)用xvid來做,ffmpeg包含的解碼庫太多,如果你只做MPPEG-4解碼,何必用這么復(fù)雜的庫?
btw,在嵌入式系統(tǒng)中最好用0.9.2版的xvid。
因為1.1.0的版本包含了很多AS的特性,通常在嵌入式系統(tǒng)中都不需要用,并且也不容易實現(xiàn)。
要自己做編碼算法的話,不能總想依賴別人,最好還是需要自己花
功夫去實現(xiàn)和優(yōu)化。因此我覺得從實際出發(fā)的話,XVID0.9.2版的比1.1.0的好。
實際上,通常在主頻400MHz的平臺上,要優(yōu)化XVID的算法達到CIF實時解碼也還是很容易的,最多就一個多月
2
視頻解碼流程對解碼帶來的影響
視頻解碼優(yōu)化一般
代碼量大,而且源代碼往往是從其他地方獲取得到的,所以閱讀比較困難,更別說優(yōu)化了,最近在優(yōu)化realVideo,有幾點心得:
1)閱讀代碼前必須先熟悉流程,抓住關(guān)鍵的點,比如視頻解碼不外乎熵解碼,反量化,反變換,插值,重建,濾波,參考幀插入等。
把握住這幾個點,可以將代碼很快分離出來。
2)分析解碼流程,了解解碼需要的最小buffer是多大,各個buffer
的位寬多大。
3)根據(jù)已經(jīng)知道的流程,跟蹤代碼buffer流向,是否存在多余的內(nèi)存拷貝。想辦法將buffer減少,經(jīng)驗說明,減少buffer帶來的速度上的提升遠(yuǎn)大于局部算法的優(yōu)化。
->耦合
4)觀察程序結(jié)構(gòu)順序是否合理,不合理的程序結(jié)構(gòu)會導(dǎo)致buffer增大。
這兩天研究視頻解碼順序,發(fā)現(xiàn)先插值后做反變換要比先做反變換再做插值效率要高許多,原因是插值后的位寬是8bits,而往往反變換后是9bits,所以在重建之前要保存插值后的值要比保存反變換后的值要省一半的空間,這樣在重建時訪問的內(nèi)存就少很多了。據(jù)我了解,大部分高效率的解碼器都是先插值再反變換,而且變換后馬上做重建,這樣既減少內(nèi)存使用,也避免內(nèi)存訪問抖動太厲害,最終減少緩存不命中。
->修改解碼流程 耦合
3 cache機制對解碼帶來的影響
先看 http://www.hongen.com/pc/diy/know/mantan/cache0.htm
寫透(直寫式)和寫回(回寫式)有著截然不同的操作,在不同的場合,不同的內(nèi)存塊使用不同的回寫策略(如果你的系統(tǒng)可以實現(xiàn)的話)要比使用一種策略要高效得多。具體一點,對于反復(fù)存取的內(nèi)存塊置成寫回,而把一次寫入而很長時間以后再使用的內(nèi)存置為寫透,可以大大提高cache的效率。
第一點很容易理解,第二點就需要琢磨一下了,由于寫透的操作是,當(dāng)緩存有該地址的數(shù)據(jù)時同時更新緩存和主存,當(dāng)緩存沒有該地址數(shù)據(jù)直接寫主存,忽略緩存。當(dāng)該地址的數(shù)據(jù)很長時間后才被使用到,那么在使用的時候該數(shù)據(jù)肯定不在cache中(被替換了),所以不如直接寫入主存來得直接;
相反,如果使用寫回操作,當(dāng)
cache中有該地址數(shù)據(jù),需要更新該數(shù)據(jù),設(shè)置dirty位,很長時間后再使用該數(shù)據(jù)或被替換的時候才將其刷進主存,這有占了茅坑不拉屎的嫌疑;而當(dāng)
cache沒有該地址數(shù)據(jù)時,情況更糟糕,首先需要將相應(yīng)的主存數(shù)據(jù)(一個cache
line)導(dǎo)入cache,再更新數(shù)據(jù),設(shè)置dirty位,再等待被刷回內(nèi)存,這種情況不僅占用了cache的空間,還多一次從主存中導(dǎo)入數(shù)據(jù)的過程,同樣占據(jù)總線,開銷很大。至于為什么要先從主存中導(dǎo)入數(shù)據(jù),是因為cache往主存回寫數(shù)據(jù)時是按照一個cache
line 單位來寫的,但被更新的數(shù)據(jù)可能沒有一個cache
line這么多,所以為了保證數(shù)據(jù)一致性,必須先把數(shù)據(jù)導(dǎo)入cache,更新后再刷回來。
對于很多視頻解碼來說,幀寫入過程是一個一次性的動作,只有在下一次作為參考幀時才會被使用到,所以幀緩沖內(nèi)存可以設(shè)置為寫透操作,而下一次使用它的時候很可能是作為參考幀來使用,而作為參考幀不需要反復(fù)的存取,只需一次讀操作就可以了,所以效率并不會因為不經(jīng)過cache而降低。實驗證明該方法可以使
mpeg4 sp解碼提高20-30%的效率。
相似的內(nèi)容cache操作的小技巧還有prefetch操作,prefetch操作是將主存的數(shù)據(jù)導(dǎo)入cache而期間cpu不需要等待,繼續(xù)下一條指令的執(zhí)行,如果下一條指令也是總線的操作,那么就必須等待prefetch完成以后再開始。所以,在使用該指令時,在prefetch指令后面插入盡可能大于一次緩存不命中所需要的clock數(shù)對應(yīng)的指令,那么prefetch與其后面的指令可以并行執(zhí)行,從而省去了等待的過程,相當(dāng)于抵消緩存不命中的損失。當(dāng)然,如果插入的指令太多而cache太小,有可能prefetch的數(shù)據(jù)進入cache
后又被替換掉了,所以,這需要自己去評估。 ->cache優(yōu)化
4
總結(jié)
IDCT是視頻解碼中關(guān)鍵步驟中的第一步,目前一般采用快速算法來做,如chen-wang
算法,c語言和匯編的效果差別還是比較大的。
對一個8x8的block做idct做變換,如
for (i = 0; i < 8;
i++)
idct_row (block + 8 * i);
for (i = 0; i < 8; i++)
idct_col
(block + i);
把他匯編后,主要是可以減少存儲器帶寬,提高存儲效率,避免無謂的內(nèi)存讀寫。
mplayer
在此方面做了很多努力,針對armv4(s3c2440屬于armv4l架構(gòu))的相關(guān)文件放在dsputil_arm_s.S文件中。但遺憾的是,它里面有一條指令PLD,cache預(yù)取指令2440是不支持的。PLD指令屬于enhanced
DSP指令,在armv4E(E 既代表enhanced
DSP)才被支持,因此在我們orchid上跑的代碼必須注釋掉這條指令,否則編譯不過
再把話題轉(zhuǎn)回來,在IDCT之前,視頻壓縮流通過VLD(variable
lenght
decode)變長解碼得到DCT數(shù)據(jù)。
這部分工作一般是通過查表來加速性能,所有的編碼表會預(yù)先存起來。而取視頻比特流的代碼通常是宏,
通過宏的擴展來達到和匯編同樣的效果。
在IDCT后還有關(guān)鍵的運動補償和色彩空間轉(zhuǎn)換兩個步驟。對運動補償?shù)募铀僖彩峭ㄟ^匯編化,其代碼也同樣放在
dsputil_arm_s.S
有必要一提的是在這部分,如果有SIMD指令將會極大的提高它的速度。
color
space轉(zhuǎn)換是解碼輸出后的最重要的一步。在嵌入式系統(tǒng)中,一般都是采用rgb565既16bit來表示一個像素的色彩。
一個8x8的block,它的yuv(420格式)表示如下,
YYYYYYYY
YYYYYYYY
YYYYYYYY
YYYYYYYY
UUUUUUUU
VVVVVVVV
注意它的值是8bit的,通過裝換方程計算,可以得到像素值。在實現(xiàn)中通常采用查表來加速計算,對于每一個Y,U,V都有
一個對應(yīng)表。對于1個320x240的video,共76800像素。如果每個像素在這個轉(zhuǎn)換中節(jié)省10個cycle,那save下來的cpu還是相當(dāng)可觀的。
當(dāng)色彩空間轉(zhuǎn)換完后,就是通過把這個picture
copy到framebuffer的內(nèi)存里,這里存在一大片的copy時間。有兩方面可以注意,
一是有人實現(xiàn)過把轉(zhuǎn)換后的內(nèi)存直接往framebuffer送,減少最后所需的copy過程,這個idea確實不錯,但是需要一些技巧去實現(xiàn)
二是copy這個過程本省也是可以加速的,在armv5以上的體系結(jié)構(gòu)里,cpu----cache---memory,其中cache和memory的寬度是32位,
但cpu和cache的bus
width確是64位,用32位的成本實現(xiàn)了64位的存儲器。如果這個能被使用,那么理論上,copy速度可以加倍。
在PC機上,一般我們的應(yīng)用程序會有fastmemorycopy這個函數(shù),它們是用simd等特殊指令來實現(xiàn),在armv5上則是通過它的總線寬度來加速
在s3c2440上不可用:(
它是v4架構(gòu)。
總的來說,
(1)算法級的優(yōu)化基本用無可用,ffmpeg/mplayer已經(jīng)實現(xiàn)的相當(dāng)不錯,除非自己實現(xiàn)一個新的decoder;
(2)在代碼級,主要是通過關(guān)鍵代碼的inline(宏,inline函數(shù))和匯編來加速。這部分在arm平臺還是有一些潛力可挖
(3)硬件級,在這一層,cpu的體系結(jié)構(gòu)決定指令集、cache的形式和大小等。如指令集是否有enhanced
DSP指令、SIMD指令
,cache是否可配置、cache
line大小,這些都會影響代碼級和算法級的優(yōu)化
(4)系統(tǒng)層優(yōu)化,之所以把它放在最后一層,是由于它建立在整個系統(tǒng)之上,只有對整個系統(tǒng)包括硬件和軟件有深刻的理解才能做到。
縱觀優(yōu)化,其實質(zhì)是盡可能的去除冗余計算,最大化的利用系統(tǒng)硬件資源。
對于RISC架構(gòu)的cpu來講,先天不足的就是需要比較大的存儲器帶寬(因為RISC的指令都是基于寄存器的,必須把操作數(shù)都load到內(nèi)存才能計算),
cpu資源被過多的使用在內(nèi)存的read和write。
以以下代碼為例,它是解碼輸出后,把yuv空間裝換成rgb空間的一個片斷
000111c
:
111c: e92d4ff0 stmdb sp!, {r4, r5, r6, r7, r8,
r9, sl, fp, lr}
1120: e1a0a000 mov sl, r0
1124: e5900038 ldr r0, [r0, #56]
1128:
e1a0c001 mov ip, r1
112c: e3500004 cmp r0,
#4 ; 0x4
1130: e24dd034 sub sp, sp, #52 ;
0x34
1134: e1a00002 mov r0, r2
1138:
e1a01003 mov r1, r3
113c: 0a00055d beq 157c
1140: e59d2058 ldr r2, [sp, #88]
1144:
e3520000 cmp r2, #0 ; 0x0
1148: d1a00002 movle
r0, r2
114c: da00055b ble 1574
1150:
e59d3060 ldr r3, [sp, #96]
1154: e58d1030 str
r1, [sp, #48]
1158: e5933000 ldr r3, [r3]
115c: e59f2434 ldr r2, [pc, #1076] ; 1598
<.text+0x1598>
1160: e0213193 mla r1, r3, r1,
r3
1164: e58d3018 str r3, [sp, #24]
1168:
e59d305c ldr r3, [sp, #92]
116c: e58d1000 str
r1, [sp]
1170: e5933000 ldr r3, [r3]
1174:
e79a1002 ldr r1, [sl, r2]
1178: e58d301c str
r3, [sp, #28]
117c: e5903008 ldr r3, [r0, #8]
1158: e5933000 ldr r3, [r3]
115c: e59f2434
ldr r2, [pc, #1076] ; 1598 <.text+0x1598>
1160:
e0213193 mla r1, r3, r1, r3
1164: e58d3018
str r3, [sp, #24]
1168: e59d305c ldr r3, [sp,
#92]
116c: e58d1000 str r1, [sp]
1170:
e5933000 ldr r3, [r3]
1174: e79a1002 ldr r1,
[sl, r2]
1178: e58d301c str r3, [sp, #28]
117c: e5903008 ldr r3, [r0, #8]
1180:
e59c4008 ldr r4, [ip, #8]
1184: e590e000 ldr
lr, [r0]
1188: e59c2000 ldr r2, [ip]
118c:
e5900004 ldr r0, [r0, #4]
1190: e59cc004 ldr
ip, [ip, #4]
1194: e58d3014 str r3, [sp, #20]
1198: e1a011c1 mov r1, r1, asr #3 ;h_size
119c:
e3a03000 mov r3, #0 ; 0x0
11a0: e58d4010 str
r4, [sp, #16]
11a4: e58d2004 str r2, [sp, #4]
11a8: e58de020 str lr, [sp, #32]
11ac:
e58d000c str r0, [sp, #12]
11b0: e58dc008 str
ip, [sp, #8]
11b4: e58d1028 str r1, [sp, #40]
11b8: e58d3024 str r3, [sp, #36]
11bc:
e1a08003 mov r8, r3
.................................................
.................................................
我們可以發(fā)現(xiàn)在這個片斷中有太多的ldr(load,
read from memory)和str(store, wirte to
memory)
而且過多的load和str還影響了cpu和memory之間的cache的效率,形成cache抖動。當(dāng)發(fā)生cache
miss時,
cahce控制器花了大力氣把內(nèi)容從memory搬到cache,但是沒怎么用這個entry馬上又被替換掉。如果運氣不好,
cache就一直這樣"抖動"。
在解碼過程中,各個模塊都各自為戰(zhàn),都各自去占比較大的memory帶寬
如何減少這種無用的行為呢?必須讓關(guān)鍵代碼適應(yīng)硬件體系結(jié)構(gòu),把數(shù)據(jù)流相關(guān)的代碼耦合在一起。
很多代碼通過模塊化得到了優(yōu)秀的可讀性和可擴展性。魚與熊掌不可兼得,耦合在一起的代碼會顯得比較晦澀難懂。
ffmpeg/mplayer在這方面作了一個比較好的tradeoff。
1、2、3的知識摘自網(wǎng)上,要比較好的理解以上內(nèi)容需要一些視頻編、解碼的知識。