這段時間,在做一個動態配置錄相預覽幀上的字符樣式以及顏色等等的功能。因爲要移植到幾個不同的項目上,剛好這幾個項目的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/70227199和https://blog.csdn.net/laikaikai/article/details/89377306這兩篇博客裏的資源。