[email protected]
2008.03.23
tag: YUV,YCbCr,YUV到RGB顏色轉換,YUV解碼,VFW,視頻,MMX,SSE,多核優化
摘要:
我們得到的很多視頻數據(一些解碼器的輸出或者攝像頭的輸出等)都使用了一種
叫YUV的顏色格式;本文介紹了常見的YUV視頻格式(YUY2/YVYU/UYVY/I420/YV12等)到
RGB顏色格式的轉換,並嘗試對轉化的速度進行優化;
全文 分爲:
《上篇》文章首先介紹了YUV顏色格式,並介紹了YUV顏色格式和RGB顏色格式之
間的相互轉換;然後重點介紹了YUYV視頻格式到RGB32格式的轉化,並嘗試進行了一
些速度優化;
《中篇》嘗試使用MMX/SSE指令對前面實現的解碼器核心進行速度優化;然
後簡要介紹了一個使用這類CPU特殊指令時的代碼框架,使得解碼程序能夠根據運行時
的CPU指令支持情況動態調用最佳的實現代碼;並最終提供一個多核並行的優化版本;
《下篇》介紹YUV類型的其他種類繁多的視頻數據編碼格式;並將前面實現的解碼
器核心(在不損失代碼速度的前提下)進行必要的修改,使之適用於這些YUV視頻格式
的解碼;
正文:
代碼使用C++,編譯器:VC2005
涉及到彙編的時候假定爲x86平臺;
現在的高清視頻幀尺寸越來越大,所以本文測試的圖片大小將使用1024x576和
1920x1080兩種常見的幀尺寸來測試解碼器速度;
測試平臺:(CPU:AMD64x2 4200+(2.37G); 內存:DDR2 677(雙通道); 編譯器:VC2005)
(另一套測試平臺(Intel Core2 4400)不再由我使用,換成了蘋果的iMac電腦)
請先參看《YUV視頻格式到RGB32格式轉換的速度優化 上篇》和《... 中篇》;
A:YUV視頻格式的分類:
YUV數據有很多種儲存的方式:
從數據佈局方式來看,YUV數據主要分爲兩大類packed
模式和planar模式;packed模式是指Y/U/V顏色分量放置在一起,比如前面的YUYV格式,
它就是兩個相鄰像素打包在一起;planar模式是指把Y/U/V顏色分量分成3個大區存放,
也就是所有的Y連續儲存在一起,同樣所有的U和V也連續儲存在一起,比如常見的I420
格式。
從數據壓縮的角度來看,YUV數據主要的模式有: 1:1:1
、2:1:1、4:1:1等模式;
1:1:1模式是指Y/U/V的數據量一樣,一個像素對應一組YUV數據(在視頻編碼中比較少
見);2:1:1模式是指兩個像素對應兩個Y數據和一個U和一個V數據,由於人眼對亮度(Y)
更敏感,所以就壓縮了U/V分量的數量,比如把相鄰的兩個像素的U/V分量取平均值,然
後這兩個像素共享這組U/V值,前面介紹的YUYV格式就屬於2:1:1模式;
4:1:1模式也很好
理解,就是把2x2範圍的4個相鄰像素一起編碼得到4個Y分量,然後4個像素共享這組U/V
值,I420格式就屬於這類;
B:我們來實現planar模式的YUV數據解碼
const TUInt8* pU,const TUInt8* pV,long width)
{
for (long x=0;x<width;++x)
pDstLine[x]=YUVToRGB32_Int(pY[x],pU[x],pV[x]);
}
//1:1:1 planar模式
void DECODE_PlanarYUV111_Common(const TUInt8* pY,const long Y_byte_width,
const TUInt8* pU,const long U_byte_width,
const TUInt8* pV,const long V_byte_width,
const TPicRegion& DstPic)
{
assert((DstPic.width & 1)==0);
TARGB32* pDstLine=DstPic.pdata;
for (long y=0;y<DstPic.height;++y)
{
DECODE_PlanarYUV111_Common_line(pDstLine,pY,pU,pV,DstPic.width);
((TUInt8*&)pDstLine)+=DstPic.byte_width;
pY+=Y_byte_width;
pU+=U_byte_width;
pV+=V_byte_width;
}
}
const TUInt8* pU,const TUInt8* pV,long width)
{
for (long x=0;x<width;x+=2)
{
long x_uv=x>>1;
YUVToRGB32_Two(&pDstLine[x],pY[x],pY[x+1],pU[x_uv],pV[x_uv]);
}
}
//2:1:1 planar模式
void DECODE_PlanarYUV211_Common(const TUInt8* pY,const long Y_byte_width,
const TUInt8* pU,const long U_byte_width,
const TUInt8* pV,const long V_byte_width,
const TPicRegion& DstPic)
{
assert((DstPic.width & 1)==0);
TARGB32* pDstLine=DstPic.pdata;
for (long y=0;y<DstPic.height;++y)
{
DECODE_PlanarYUV211_Common_line(pDstLine,pY,pU,pV,DstPic.width);
((TUInt8*&)pDstLine)+=DstPic.byte_width;
pY+=Y_byte_width;
pU+=U_byte_width;
pV+=V_byte_width;
}
}
void DECODE_PlanarYUV411_Common(const TUInt8* pY,const long Y_byte_width,
const TUInt8* pU,const long U_byte_width,
const TUInt8* pV,const long V_byte_width,
const TPicRegion& DstPic)
{
assert((DstPic.width & 1)==0);
TARGB32* pDstLine=DstPic.pdata;
for (long y=0;y<DstPic.height;++y)
{
DECODE_PlanarYUV211_Common_line(pDstLine,pY,pU,pV,DstPic.width);
((TUInt8*&)pDstLine)+=DstPic.byte_width;
pY+=Y_byte_width;
//這裏做了特殊處理,使Y下移兩行的時候U、V纔會下移一行
if ((y&1)==1)
{
pU+=U_byte_width;
pV+=V_byte_width;
}
}
}
一點說明:
1:1:1模式,後面將不再處理,而4:1:1模式直接使用了2:1:1解碼器的核心;
C.我們來優化DECODE_PlanarYUV411_Common函數;
1.當前的實現DECODE_PlanarYUV411_Common
速度測試:
/////////////////////////////////////////////////////////
//=======================================================
//
| 1024x576 | 1920x1080
|
//-------------------------------------------------------
//
| AMD64x2 | AMD64x2 |
//-------------------------------------------------------
//DECODE_PlanarYUV411_Common
236.1 FPS 67.5 FPS
/////////////////////////////////////////////////////////
2.MMX的實現DECODE_PlanarYUV411_MMX
#define PlanarYUV211_Loader_MMX(in_y_reg,in_u_reg,in_v_reg) /
asm movd mm1,[in_u_reg] /*mm1=00 00 00 00 U3 U2 U1 U0 */ /
asm movd mm2,[in_v_reg] /*mm2=00 00 00 00 V3 V2 V1 V0 */ /
asm pxor mm4,mm4 /*mm4=00 00 00 00 00 00 00 00 */ /
asm movq mm0,[in_y_reg] /*mm0=Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 */ /
asm punpcklbw mm1,mm4 /*mm1=00 U3 00 U2 00 U1 00 U0 */ /
asm punpcklbw mm2,mm4 /*mm2=00 V3 00 V2 00 V1 00 V0 */
void DECODE_PlanarYUV211_MMX_line(TARGB32* pDstLine,const TUInt8* pY,
const TUInt8* pU,const TUInt8* pV,long width)
{
long expand8_width=(width>>3)<<3;
if (expand8_width>0)
{
asm
{
push esi
push edi
mov ecx,expand8_width
shr ecx,1
mov eax,pY
mov esi,pU
mov edi,pV
mov edx,pDstLine
lea eax,[eax+ecx*2]
lea esi,[esi+ecx]
lea edi,[edi+ecx]
neg ecx
loop_beign:
PlanarYUV211_Loader_MMX(eax+ecx*2,esi+ecx,edi+ecx)
YUV422ToRGB32_MMX(edx,movq)
add edx,8*4
add ecx,4
jnz loop_beign
mov pY,eax
mov pU,esi
mov pV,edi
mov pDstLine,edx
pop edi
pop esi
}
}
//處理邊界
DECODE_PlanarYUV211_Common_line(pDstLine,pY,pU,pV,width-expand8_width);
}
void DECODE_PlanarYUV411_MMX(const TUInt8* pY,const long Y_byte_width,
const TUInt8* pU,const long U_byte_width,
const TUInt8* pV,const long V_byte_width,
const TPicRegion& DstPic)
{
assert((DstPic.width & 1)==0);
TARGB32* pDstLine=DstPic.pdata;
for (long y=0;y<DstPic.height;++y)
{
DECODE_PlanarYUV211_MMX_line(pDstLine,pY,pU,pV,DstPic.width);
((TUInt8*&)pDstLine)+=DstPic.byte_width;
pY+=Y_byte_width;
if ((y&1)==1)
{
pU+=U_byte_width;
pV+=V_byte_width;
}
}
asm emms
}
速度測試:
/////////////////////////////////////////////////////////
//=======================================================
//
| 1024x576 | 1920x1080
|
//-------------------------------------------------------
//
| AMD64x2 | AMD64x2 |
//-------------------------------------------------------
//DECODE_PlanarYUV411_MMX
650.1 FPS 187.3 FPS
/////////////////////////////////////////////////////////
3.優化寫緩衝的SSE實現DECODE_PlanarYUV411_SSE
const TUInt8* pU,const TUInt8* pV,long width)
{
long expand8_width=(width>>3)<<3;
if (expand8_width>0)
{
asm
{
push esi
push edi
mov ecx,expand8_width
shr ecx,1
mov eax,pY
mov esi,pU
mov edi,pV
mov edx,pDstLine
lea eax,[eax+ecx*2]
lea esi,[esi+ecx]
lea edi,[edi+ecx]
neg ecx
loop_beign:
PlanarYUV211_Loader_MMX(eax+ecx*2,esi+ecx,edi+ecx)
YUV422ToRGB32_SSE(edx)
add edx,8*4
add ecx,4
jnz loop_beign
mov pY,eax
mov pU,esi
mov pV,edi
mov pDstLine,edx
pop edi
pop esi
}
}
//處理邊界
DECODE_PlanarYUV211_Common_line(pDstLine,pY,pU,pV,width-expand8_width);
}
void DECODE_PlanarYUV411_SSE(const TUInt8* pY,const long Y_byte_width,
const TUInt8* pU,const long U_byte_width,
const TUInt8* pV,const long V_byte_width,
const TPicRegion& DstPic)
{
assert((DstPic.width & 1)==0);
TARGB32* pDstLine=DstPic.pdata;
for (long y=0;y<DstPic.height;++y)
{
DECODE_PlanarYUV211_SSE_line(pDstLine,pY,pU,pV,DstPic.width);
((TUInt8*&)pDstLine)+=DstPic.byte_width;
pY+=Y_byte_width;
if ((y&1)==1)
{
pU+=U_byte_width;
pV+=V_byte_width;
}
}
asm emms
}
速度測試:
/////////////////////////////////////////////////////////
//=======================================================
//
| 1024x576 | 1920x1080
|
//-------------------------------------------------------
//
| AMD64x2 | AMD64x2 |
//-------------------------------------------------------
//DECODE_PlanarYUV411_SSE
864.6 FPS 249.5 FPS
/////////////////////////////////////////////////////////
4.自動適應CPU指令集的版本和並行優化版本的實現就不贅述了;
D:解碼器框架
有了前面的各種實現的嘗試,完成支持大部分YUV視頻格式的解碼器已經沒有多少困難了;剩下的
就是弄清楚數據的儲存格式並組織規劃好各種實現代碼。
一些建議:
可以將解碼器分成3段,載入器、核心解碼器、顏色輸出器,不同的YUV格式可能需要不同的
“載入器”實現,它負責組織好Y、U、V源,使之適合核心解碼器使用,輸出的時候可能有不同
的RGB顏色編碼輸出需求,可以做幾個不同的“顏色輸出器”實現;
Planar模式的解碼是比較容易統一處理的,只要弄清楚各分量存放的位置就能使用同一個解碼器
函數的實現;
packed模式就麻煩一些,需要對不同的編碼方式實現不同的“載入器”(也可以把它們做成多個
仿函數實現,作爲解碼函數的參數;另外合理運用內聯、宏和泛型可以節省很多代碼和維護工作量;)
(最近都比較忙,這篇文章拖了3個月才完成,比計劃的內容減少了很多)