YUV視頻格式到RGB32格式轉換的速度優化 下篇

                  YUV視頻格式到RGB32格式轉換的速度優化 下篇       

                    [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數據解碼

    void DECODE_PlanarYUV111_Common_line(TARGB32* pDstLine,const TUInt8* pY,
                                         
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;
    }    
}
    void DECODE_PlanarYUV211_Common_line(TARGB32* pDstLine,const TUInt8* pY,
                                         
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;
    }
}
//4:1:1 planar模式
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

    void DECODE_PlanarYUV211_SSE_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_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個月才完成,比計劃的內容減少了很多)


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