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

news/2025/2/11 23:24:40/

       这段时间,在做一个动态配置录相预览帧上的字符样式以及颜色等等的功能。因为要移植到几个不同的项目上,刚好这几个项目的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

     


http://www.ppmy.cn/news/121764.html

相关文章

RGB、YUV420、NV21、I420编码区别

一、RGB与YUV 首先了解视频编码为什么使用YUV420而不是rgb。那么需要了解两者的原理&#xff0c;看图说话 所以从内存的角度来说&#xff0c;yuv单位像素使用的内存更低,但是两者表示的效果是一致的&#xff0c;也可以认为rgb过度绘制了&#xff0c;把人眼无法分辨的区域也进行…

YV12和I420的区别 yuv420和yuv420p的区别

YV12和I420的区别 YV12和I420的区别 一般来说&#xff0c;直接采集到的视频数据是RGB24的格式&#xff0c;RGB24一帧的大小size&#xff1d;widthheigth3 Bit&#xff0c;RGB32的size&#xff1d;widthheigth4&#xff0c;如果是I420&#xff08;即YUV标准格式4&#xff1a;2&a…

YUV420P格式分析

YUV格式通常有两大类&#xff1a;打包&#xff08;packed&#xff09;格式和平面&#xff08;planar&#xff09;格式。前者将YUV分量存放在同一个数组中&#xff0c;通常是几个相邻的像素组成一个宏像素&#xff08;macro-pixel&#xff09;&#xff1b;而后者使用三个数组分开…

详解YUV420数据格式

欢迎Follow我的GitHub, 关注我的CSDN. 其余参考Android目录.技术微信公众号&#xff1a;程序员小乐 详解YUV420数据格式 原文地址&#xff1a;http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html 1. YUV简介 YUV定义&#xff1a;分为三个分量&#xff0c;“Y”表…

yuv420格式nv12,nv21,I420,YV12互转

YUV格式有两大类&#xff1a;planar和packed。 对于planar的YUV格式&#xff0c;先连续存储所有像素点的Y&#xff0c;紧接着存储所有像素点的U&#xff0c;随后是所有像素点的V。 对于packed的YUV格式&#xff0c;每个像素点的Y,U,V是连续交*存储的。 YUV&#xff0c;分为三个…

Linux之V4L2驱动框架

目录 一、V4L2简介 二、V4L2操作流程 1.打开摄像头 2.查询设备的属性/能力/功能 3.获取摄像头支持的格式 4.设置摄像头的采集通道 5.设置/获取摄像头的采集格式和参数 6.申请帧缓冲、内存映射、入队 &#xff08;1&#xff09;申请帧缓冲 &#xff08;2&#xff09;内…

YUV422转换YUV420应用实例

在上一篇文章中JPEG编码学习—JPEG数据转YUV数据应用实例 已经可以将v4l2 采集到的JPEG数据转换为YUV422格式&#xff0c;但是我们有时候需要使用其他格式的数据&#xff0c;比如用YUV420作为H264的输入数据格式。做数据格式转换&#xff0c;首先需要明白各种数据类型的采样分布…

YUV420 总结 (YU12、YV12、NV12 和 NV21)

YUV模型是根据一个亮度(Y分量)和两个色度(UV分量)来定义颜色空间。最常见的格式为 YUV420分为两种&#xff1a;YUV420P和YUV420SP。 YUV420P YUV420P 为 plane平面模式&#xff0c;Y, U, V分量处于不同平面&#xff0c;即在 ffmpeg 中数据存放在 3 个数组中。 其可再细分为 Y…