移动端录像在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,其中试用了很多汇编指令加速,效率惊人。