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模式就麻烦一些,需要对不同的编码方式实现不同的“载入器”(也可以把它们做成多个
仿函数实现,作为解码函数的参数;另外合理运用内联、宏和泛型可以节省很多代码和维护工作量;)

你可能感兴趣的:(框架,优化,测试,byte,平台,编译器)