http://blog.csdn.net/dangxw_/article/details/50903693
移动端录像在yuv数据上存在如下问题:
1.无论Android还是iOS都不能直接从摄像头取出颜色空间为i420的数据,所以在编码前需要进行格式转换。
2.而且由于所取图像得分辨率必须是摄像头所提供分辨率中得一组,所以有可能需要裁剪。
3.另外由于1)想让无论用户哪个方向拿手机所录的视频内容永远“头朝上”,
2)摄像头默认返回图像为横屏图像(宽大于长)所以需要旋转。
4.前置摄像头需要镜像。
YUV 颜色空间分类:https://zh.wikipedia.org/wiki/YUV
yuv 420 又分为:
I420: YYYYYYYY UU VV =>YUV420P
YV12: YYYYYYYY VV UU =>YUV420P
NV12: YYYYYYYY UVUV =>YUV420SP
NV21: YYYYYYYY VUVU =>YUV420SP
下面给出解决这四个问题所需要得算法:
1 格式转换:
nv21 转成i420
- //nv21 to yuvi420
- void NV21ToI420(uint8_t* dstyuv,uint8_t* data, int imageWidth, int imageHeight)
- {
- int Ustart =imageWidth*imageHeight;
- int i,j;
- int uWidth = imageWidth/2;
- int uHeight = imageWidth/2;
- //y
- memcpy(dstyuv,data,imageWidth*imageHeight);
- int tempindex = 0 ;
- int srcindex= 0;
- //u
- for(i= 0 ;i <uHeight;i++)
- {
- for(j = 0;j <uWidth ;j++ )
- {
- dstyuv[Ustart+tempindex+j]= data[Ustart+(srcindex<<1)+1];
- srcindex++;
- }
- tempindex+= uWidth;
- }
- //v
- for (i = 0; i < uHeight;i++)
- {
- for (j = 0; j < uWidth;j++)
- {
- dstyuv[Ustart+tempindex + j] = data[Ustart + (srcindex << 1 )];
- srcindex++;
- }
- tempindex+= uWidth;
- }
- }
其实就是改变了uv的位置。
2 裁剪:
- //crop yuv data
- int crop_yuv (char* data, char*dst, intwidth, intheight,
- int goalwidth, int goalheight) {
- int i, j;
- int h_div = 0, w_div = 0;
- w_div= (width - goalwidth) / 2;
- if (w_div % 2)
- w_div--;
- h_div= (height - goalheight) / 2;
- if (h_div % 2)
- h_div--;
- //u_div = (height-goalheight)/4;
- int src_y_length = width *height;
- int dst_y_length =goalwidth * goalheight;
- for (i = 0; i <goalheight; i++)
- for (j = 0; j <goalwidth; j++) {
- dst[i* goalwidth + j] = data[(i + h_div) * width + j + w_div];
- }
- int index = dst_y_length;
- int src_begin =src_y_length + h_div * width / 4;
- int src_u_length =src_y_length / 4;
- int dst_u_length =dst_y_length / 4;
- for (i = 0; i <goalheight / 2; i++)
- for (j = 0; j <goalwidth / 2; j++) {
- int p = src_begin + i *(width >> 1) + (w_div >> 1) + j;
- dst[index]= data[p];
- dst[dst_u_length+ index++] = data[p + src_u_length];
- }
- return 0;
- }
3 旋转:
分为四个方向
旋转:
以顺时针旋转270度为例作图:
Y1 | Y2 | Y3 | Y4 |
Y5 | Y6 | Y7 | Y8 |
Y9 | Y10 | Y11 | Y12 |
Y13 | Y14 | Y15 | Y16 |
U1 | U2 | U3 | U4 |
V1 | V2 | V3 | V4 |
原图
Y4 | Y8 | Y12 | Y16 |
Y3 | Y7 | Y11 | Y15 |
Y2 | Y6 | Y10 | Y14 |
Y1 | Y5 | Y9 | Y13 |
U2 | U4 | U1 | U3 |
V2 | V4 | V1 | V3 |
旋转后
u值的第i 行j列 对应原 数据的下标为: ustart+uw*j-i;
去除index的乘除法运算后:
//i420 顺时针 270度
- int rotateYUV420Degree270(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight) {
- int i = 0, j = 0;
- int index = 0;
- int tempindex = 0;
- int div = 0;
- for (i = 0; i <imageHeight; i++) {
- div= i +1;
- tempindex= 0;
- for (j = 0; j <imageWidth; j++) {
- tempindex+= imageWidth;
- dstyuv[index++]= srcdata[tempindex-div];
- }
- }
- int start =imageWidth*imageHeight;
- int udiv = imageWidth *imageHeight / 4;
- int uWidth = imageWidth /2;
- int uHeight = imageHeight /2;
- index= start;
- for (i = 0; i < uHeight;i++) {
- div= i +1;
- tempindex= start;
- for (j = 0; j < uWidth;j++) {
- tempindex += uWidth;
- dstyuv[index]= srcdata[tempindex-div];
- dstyuv[index+udiv]= srcdata[tempindex-div+udiv];
- index++;
- }
- }
- return 0;
- }
//i420 顺时针旋转 180;
- int rotateYUV420Degree180(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight)
- {
- int i = 0, j = 0;
- int index = 0;
- int tempindex = 0;
- int ustart = imageWidth *imageHeight;
- tempindex= ustart;
- for (i = 0; i <imageHeight; i++) {
- tempindex-= imageWidth;
- for (j = 0; j <imageWidth; j++) {
- dstyuv[index++] = srcdata[tempindex + j];
- }
- }
- int udiv = imageWidth *imageHeight / 4;
- int uWidth = imageWidth /2;
- int uHeight = imageHeight /2;
- index= ustart;
- tempindex= ustart+udiv;
- for (i = 0; i < uHeight;i++) {
- tempindex-= uWidth;
- for (j = 0; j < uWidth;j++) {
- dstyuv[index]= srcdata[tempindex + j];
- dstyuv[index+ udiv] = srcdata[tempindex + j + udiv];
- index++;
- }
- }
- return 0;
- }
顺时针 90度:
- //i420顺时针旋转90 ;
- int rotateYUV420Degree90(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight) {
- int i = 0, j = 0;
- int index = 0;
- int tempindex = 0;
- int div = 0;
- int ustart = imageWidth *imageHeight;
- for (i = 0; i <imageHeight; i++) {
- div= i;
- tempindex= ustart;
- for (j = 0; j <imageWidth; j++) {
- tempindex-= imageWidth;
- dstyuv[index++]= srcdata[tempindex + div];
- }
- }
- int udiv = imageWidth *imageHeight / 4;
- int uWidth = imageWidth /2;
- int uHeight = imageHeight /2;
- index= ustart;
- for (i = 0; i < uHeight;i++) {
- div= i ;
- tempindex= ustart+udiv;
- for (j = 0; j < uWidth;j++) {
- tempindex-= uWidth;
- dstyuv[index]= srcdata[tempindex + div];
- dstyuv[index+ udiv] = srcdata[tempindex + div + udiv];
- index++;
- }
- }
- return 0;
- }
如果从摄像头取出数据,这样一步步的历遍,在低配手机上是满足不了需求的。其实这三个步骤中有很多中间步骤是可以省去的,比如:将a放到b 位置,再将b位置上的数据取出放到c位置,那么可以直接将a放到c位置。
所以需要优化以上三类问题所用的算法将其整合。结果如下:
- void detailPic0(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {
- int deleteW = (nw - w) / 2;
- int deleteH = (nh - h) / 2;
- //处理y 旋转加裁剪
- int i, j;
- int index = 0;
- for (j = deleteH; j < nh- deleteH; j++) {
- for (i = deleteW; i < nw- deleteW; i++)
- yuv_temp[index++]= d[j * nw + i];
- }
- //处理u
- index= w * h;
- for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)
- for (j = deleteW + 1; j< nw - deleteW; j += 2)
- yuv_temp[index++]= d[i * nw + j];
- //处理v 旋转裁剪加格式转换
- for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)
- for (j = deleteW; j < nw- deleteW; j += 2)
- yuv_temp[index++]= d[i * nw + j];
- }
- //针对横屏前摄像头 nv21 to 420sp 裁剪,旋转
- void detailPic180(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {
- int deleteW = (nw - w) / 2;
- int deleteH = (nh - h) / 2;
- //处理y 旋转加裁剪
- int i, j;
- int index = w * h;
- for (j = deleteH; j < nh- deleteH; j++) {
- for (i = deleteW; i < nw- deleteW; i++)
- yuv_temp[--index]= d[j * nw + i];
- }
- //处理u
- index= w * h * 5 / 4;
- for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)
- for (j = deleteW + 1; j< nw - deleteW; j += 2)
- yuv_temp[--index]= d[i * nw + j];
- //处理v 旋转裁剪加格式转换
- index= w * h * 3 / 2;
- for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)
- for (j = deleteW; j < nw- deleteW; j += 2)
- yuv_temp[--index]= d[i * nw + j];
- }
- void detailPic90(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {
- int deleteW = (nw - h) / 2;
- int deleteH = (nh - w) / 2;
- int i, j;
- }*/
- for (i = 0; i < h; i++){
- for (j = 0; j < w; j++){
- yuv_temp[(h- i) * w - 1 - j] = d[nw * (deleteH + j) + nw - deleteW
- -i];
- }
- }
- int index = w * h;
- for (i = deleteW + 1; i< nw - deleteW; i += 2)
- for (j = nh / 2 * 3 -deleteH / 2; j > nh + deleteH / 2; j--)
- yuv_temp[index++]= d[(j - 1) * nw + i];
- for (i = deleteW; i < nw- deleteW; i += 2)
- for (j = nh / 2 * 3 -deleteH / 2; j > nh + deleteH / 2; j--)
- yuv_temp[index++]= d[(j - 1) * nw + i];
- }
- void detailPic270(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {
- int deleteW = (nw - h) / 2;
- int deleteH = (nh - w) / 2;
- int i, j;
- //处理y 旋转加裁剪
- for (i = 0; i < h; i++){
- for (j = 0; j < w; j++){
- yuv_temp[i* w + j] = d[nw * (deleteH + j) + nw - deleteW - i];
- }
- }
- //处理u 旋转裁剪加格式转换
- int index = w * h;
- for (i = nw - deleteW - 1;i > deleteW; i -= 2)
- for (j = nh + deleteH / 2;j < nh / 2 * 3 - deleteH / 2; j++)
- yuv_temp[index++]= d[(j) * nw + i];
- //处理v 旋转裁剪加格式转换
- for (i = nw - deleteW - 2;i >= deleteW; i -= 2)
- for (j = nh + deleteH / 2;j < nh / 2 * 3 - deleteH / 2; j++)
- yuv_temp[index++]= d[(j) * nw + i];
- }
注:没有优化,消除index的乘法后效果肯定会更好。
4 镜像:
- //mirro 原址的
- void Mirror(uint8_t* yuv_temp, int nw, int nh, int w,
- int h) {
- int deleteW = (nw - h) / 2;
- int deleteH = (nh - w) / 2;
- int i, j;
- int a, b;
- uint8_ttemp;
- //mirror y
- for (i = 0; i < h; i++){
- a= i * w;
- b= (i + 1) * w - 1;
- while (a < b) {
- temp= yuv_temp[a];
- yuv_temp[a]= yuv_temp[b];
- yuv_temp[b]= temp;
- a++;
- b--;
- }
- }
- //mirror u
- int uindex = w * h;
- for (i = 0; i < h / 2;i++) {
- a = i * w / 2;
- b= (i + 1) * w / 2 - 1;
- while (a < b) {
- temp= yuv_temp[a + uindex];
- yuv_temp[a+ uindex] = yuv_temp[b + uindex];
- yuv_temp[b+ uindex] = temp;
- a++;
- b--;
- }
- }
- //mirror v
- uindex= w * h / 4 * 5;
- for (i = 0; i < h / 2;i++) {
- a= i * w / 2;
- b= (i + 1) * w / 2 - 1;
- while (a < b) {
- temp= yuv_temp[a + uindex];
- yuv_temp[a+ uindex] = yuv_temp[b + uindex];
- yuv_temp[b+ uindex] = temp;
- a++;
- b--;
- }
- }
- }
由于当初忽略了镜像,所以并没有把镜像也和其他三个算法和并到一起。不过测试还是通过的。
如果集成ffmpeg或者OpenCV,可以使用ffmpeg的sws,filter 或者opencv的cvtcolor都可以轻松实现部分功能,不过跟踪过ffmpeg sws的源码,发现效率较低,代码实现较差。如果追求处理效率,并且以上优化的算法仍不能满足,建议使用libyuv,其中试用了很多汇编指令加速,效率惊人。