YUV420之YV12格式以及yuv422格式的顯示

        這段時間,在做一個動態配置錄相預覽幀上的字符樣式以及顏色等等的功能。因爲要移植到幾個不同的項目上,剛好這幾個項目的camera原始預覽數據格式,一個爲yv12,一個yuv422,所以將這兩種格式都做了送顯的處理。先上一段傳統的代碼,也就是網上流行的給camera幀打上時間戳的代碼:

DisplayClient::
addPreviewTimestamps(sp<StreamImgBuf>const& pCameraImgBuf)
{
      int width = pCameraImgBuf->getImgWidth();
      int height = pCameraImgBuf->getImgHeight();
      //ALOGD("timestamp videoSize  width : %d, height : %d",width,height);
      bool is1080P = width > 1280;
      int word_width = is1080P? digital_1080_d_width : digital_720_d_width;
      int word_height = is1080P? digital_1080_d_height : digital_720_d_height;
      int word_gap = is1080P? digital_1080_gap_d_width : gap_720_d_width;
      uint8_t* _ptr=(uint8_t *)pCameraImgBuf->getVirAddr();
      char isTimestampOffset[PROPERTY_VALUE_MAX];
      int    offset         = height - 100;
      property_get("com.spt.stampoffset.switch",isTimestampOffset,"0");
      if('1' == *isTimestampOffset){
	  offset =  55;
	  }
      int    margin_left      = width - 18 * word_width - 100;

      if ( NULL != _ptr )
      {
          char        dateTime[] = "2014-05-29 03:16:78";
            time_t          timer;
            struct tm     * t_tm;

            time( &timer );
            t_tm = localtime( &timer );
            memset( dateTime, 0, sizeof(dateTime) );
            sprintf( dateTime, "%4d-%02d-%02d %02d:%02d:%02d", t_tm->tm_year + 1900, t_tm->tm_mon + 1, t_tm->tm_mday, t_tm->tm_hour, t_tm->tm_min, t_tm->tm_sec );

            int digitalNums[10 + 8 + 1 + 1] = { -1 }; /* 10:-       11 ::      12:blank */
            memset( digitalNums, -1, sizeof(digitalNums) );
            for ( int i = 0; i < strlen( dateTime ); i++ )
            {
                char num = dateTime[i];
                if ( ('0' <= num) && (num <= '9') )
                {
                    digitalNums[i] = num - '0';
                }else if ( num == '-' )
                {
                    digitalNums[i] = 10;
                }else if ( num == ':' )
                {
                    digitalNums[i] = 11;
                }else if ( num == ' ' )
                {
                    digitalNums[i] = 12;
                }
            }

            for ( int j = 0; j < word_height; j++ )
            {
                for ( int k = 0; k < 10 + 1 + 8; k++ )
                {
                    const unsigned char* str = (digitalNums[k] < 12 && digitalNums[k] != -1) ? (is1080P? DigitalArray_1080_d[digitalNums[k]] : DigitalArray_node_d[digitalNums[k]]) : NULL;
                    if ( str != NULL )
                    {
                        for ( int h = 0; h < word_width; h++ )
                        {
                            if ( *(str + (word_height - 1 - j) * word_width + h) != 0x00 )
                            {
                                        const int     offset_pixel     = offset * width + margin_left + j * width + k * (word_gap + word_width) + h;
                                        const int     offset_adr    = (int) (offset_pixel * 1);
                                        *(_ptr + offset_adr)        = 0xff;
                                        //*(_ptr + offset_adr + 1)      = 0xff;

                            }
                        }
                    }
                }
            }
	     //add by mcjerdy specified timestamp end
      }
}

        這段代碼的核心原理,就是從字符數組裏取出編碼成了yuv422或yv12的一個個字節,來替換對應位置的內容。我們現在要做的工作,也就是這樣。只不過上面這段代碼是沒有加顏色的,也就是隻畫了Y(灰度)數據,所以算法很簡單。而我們要將顏色也畫上去,那麼就還需要將對應的u、v分量也給畫上去,算法自然也就不同了。

        再來先講下yuv數據的格式,YU12和YV12屬於YUV420格式,也是一種Plane模式,將Y、U、V分量分別打包,依次存儲。其每一個像素點的YUV數據提取遵循YUV420格式的提取方式,即4個Y分量共用一組UV。

        NV12和NV21屬於YUV420格式,是一種two-plane模式,即Y和UV分爲兩個Plane,但是UV(CbCr)爲交錯存儲,而不是分爲三個plane。

        

在YUV420中,一個像素點對應一個Y,一個4X4的小方塊對應一個U和V。對於所有YUV420圖像,它們的Y值排列是完全相同的,因爲只有Y的圖像就是灰度圖像。YUV420sp與YUV420p的數據格式它們的UV排列在原理上是完全不同的。420p它是先把U存放完後,再存放V,也就是說UV它們是連續的。而420sp它是UV、UV這樣交替存放的。(見下圖) 有了上面的理論,我就可以準確的計算出一個YUV420在內存中存放的大小。 width * hight =Y(總和) U = Y / 4   V = Y / 4

 

所以YUV420 數據在內存中的長度是 width * hight * 3 / 2,

假設一個分辨率爲8X4的YUV圖像,它們的格式如下圖:

YUV420sp格式如下圖 

YUV420p數據格式如下圖

        從上圖可以看出,yuv420sp和yuv420p的存儲方式,基本相同,只是yuv420sp的uv是交替存儲的,而yuv420p的uv是分開存儲的。我們要處理的yv12,就是屬於yuv420p的一種,不過yv12是先存的全部Y,然後再存全部的V,最後再存全部的U,這個順序不能弄亂了。

        在yv12中,所有 Y 樣例都會作爲不帶正負號的 char 值組成的數組首先顯示在內存中。此數組後面緊接着所有 V (Cr) 樣例。V 平面的跨距爲 Y 平面跨距的一半,V 平面包含的行爲 Y 平面包含行的一半。V 平面後面緊接着所有 U (Cb) 樣例,它的跨距和行數與 V 平面相同, 見下圖:

        有了上面的基礎,我們再來說說加yv12時間戳水印的事。因爲我們camera出來的原始數據就是yv12的,所以我們要用來替換的數字圖片數組,必定也是轉成了yv12的無符號字符數組unsigned char ptr[]。也就是數且的前面w*h個字節,存儲的是Y數據。後面緊接着從ptr[w*h - 1]開始,一共存儲了w/2 * h/2個V字符。再從ptr[w*h + w/2 * h/2 -1]開始,存儲剩下的w/2 * h/2個U字符。 以width=4, height=8爲例,總大小爲4*8*1.5=32*1.5=48個字節。 ptr[0]~ptr[31]存儲的是Y數據, ptr[32]~ptr[39]存儲的是V數據,ptr[40]~ptr[47]存儲的是U數據。好了,接下來上畫yv12的代碼:

inline void DisplayClient::fill_yv12( int x,int y, unsigned char* camera_ptr,int cameraWidth,int cameraHeight,unsigned char* pic_ptr, int picWidth,int picHeight )
{
    int     offset_pixel = 0;
    int index = 0;
    for ( int j = 0; j < picHeight; j++ )
    {
        for ( int h = 0; h < picWidth; h++ )
        {
            offset_pixel     = y * cameraWidth + x + j * cameraWidth + h;
            index  = j*picWidth+h;
            *(camera_ptr + offset_pixel) = pic_ptr[index];                            
        }
    }
}

       這個fill_yv12函數,每調一次,只單獨畫Y、U、V這三個分量中的一個。x, y是指從一幀圖片的哪個座標開始畫, camera_ptr是這一幀圖片的起始地址,cameraWidth是一幀的寬度, cameraHeight是幀的高度,pic_ptr是用來替換幀像素的圖片,比如對應的“0”、“1”等數據圖片的地址, picWidth、picHeight是數字圖片的寬高。

        調用fill_yv12的代碼如下:

uint8_t* _ptr=(uint8_t *)pCameraImgBuf->getVirAddr();
int half_height = picHieght/2;
int half_width = picWidth/2;
int half_camera_height = mCameraHeight/2;
int half_camera_widht = mCameraWidth/2;
int half_x = x/2;
int half_y = y/2;
int pic_start_pos = y * mCameraWidth + x;
uint8_t* v_start_ptr = _ptr+(mCameraWidth*mCameraHeight);
uint8_t* u_start_ptr = v_start_ptr + mCameraWidth/2 * mCameraHeight/2;
unsigned char* pic_v_ptr = prefix+(picWidth*picHieght);
unsigned char* pic_u_ptr = pic_v_ptr + half_width*half_height;                        
//畫Y
fill_prefix_yv12(x, y, _ptr, mCameraWidth, mCameraHeight, prefix, picWidth, picHieght);
//畫v
fill_prefix_yv12(half_x, half_y, v_start_ptr, half_camera_widht, half_camera_height, pic_u_ptr, half_width, half_height);
//畫U
fill_prefix_yv12(half_x, half_y, u_start_ptr, half_camera_widht, half_camera_height, pic_v_ptr, half_width, half_height);                   

        爲了讓大家有個更直觀的理解,再上一個從yuv字符數組裏取uv分量的函數:

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 獲取 UV 分量
typedef unsigned char UCHAR, BYTE, *PUCHAR, *PBYTE;
VOID CRawImage::GetUV(PBYTE pbX, PBYTE *ppbU, PBYTE *ppbV)
{
	_Assert(ppbU && ppbV);

	if (m_csColorSpace == CS_YV12)
	{
		*ppbV = pbX + m_uWidth * m_uHeight;
		*ppbU = *ppbV + m_uWidth/2 * m_uHeight / 2;
	}
}

        總之一句話,畫yuv字符時,先畫y的值,然後盞V、U的值。 畫V、U的值的時候,對應的x、y座標,以及寬高都爲y的一半。

        好了,上面入是畫yv12的代碼。 下面再說一下畫yuv422的的方法,準確來說,是YUYV,它是Y1U0, Y2V0, Y3U1, Y4U1這樣yuv交替存儲的, 相鄰的兩個Y共用其相鄰的兩個U、V。對應的還有yuv422p,YUV422P也屬於YUV422的一種,它是一種Plane模式,即平面模式,並不是將YUV數據交錯存儲,而是先存放所有的Y分量,然後存儲所有的U(Cb)分量,最後存儲所有的V(Cr)分量,YUV422佔用內存空間 = w * h * 2。

         有了上面這些概念,再來上畫yuv422,也即yuyv的代碼:

inline void fill_yuv422(uint8_t* camera_ptr, unsigned char* pic_ptr, int y, int x, int bitsPerPixe)
{
    int index = 0;
    for ( int j = 0; j < mWord_height; j++ )
    {
        for ( int h = 0; h < mPrefixWidth; h++ )
        {
            const int     offset_pixel     = y * mCameraWidth + x + j * mCameraWidth + h;
            const int     offset_adr    = (int) (offset_pixel * bitsPerPixe);
            index = j*mPrefixWidth*2+h*2;
            *(camera_ptr + offset_adr) = pic_ptr[index];
            if(index+3 >= mWord_height*mPrefixWidth*2)
            {
                //如果顏色顯示正常,就用下面這條代碼
                *(camera_ptr + offset_adr + 1) = pic_ptr[index+1];
            }
            else
            {
                //如果顏色反了,則可以用下的代碼,將u和v分量的位置換一下。
                *(camera_ptr + offset_adr + 1) = pic_ptr[index+3];
            }                               
        }
    }
}

       fill_yuv422的參數camera_ptr,是指幀圖片的地址,  pic_ptr是數字圖片的地址, y, x是要畫的數字圖片的座標, bitsPerPixe是指每一個像素點佔幾個字節。當爲yuv422時,每一個像素點佔兩個字節。 *(camera_ptr + offset_adr) = pic_ptr[index];這一行是畫Y數據。 下面的是畫U和V

       上面的圖片引用到了https://blog.csdn.net/tq384998430/article/details/70227199https://blog.csdn.net/laikaikai/article/details/89377306這兩篇博客裏的資源。

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