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个月才完成,比计划的内容减少了很多)