方式1、 直接保存视频预览的数据帧
在预览方法中imgData数据就是视频帧
android默认的视频采集格式是NV21,(属于YUV420)
在onPreviewFrame中传进来的byte[] data即为NV21格式。
NV21 的存储格式是,以4 X 4 图片为例子
占用内存为 4 X 4 X 3 / 2 = 24 个字节
Y Y Y Y
Y Y Y Y
Y Y Y Y
Y Y Y Y
V U V U
V U V U
NV12 的存储格式是,以4 X 4 图片为例子
Y Y Y Y
Y Y Y Y
Y Y Y Y
Y Y Y Y
U V U V
U V U V
————————————————
public void onPreviewFrame(final byte[] imgData, final Camera camera) {
int width = camera.getParameters().getPreviewSize().width;//获取视频的宽度
int height = camera.getParameters().getPreviewSize().height;//获取照片的高度
//得到宽和高 就知道了数组大小
//imgdata数组长度和 width height 关系 => imgdata.length = width * height *3/2
//byte[] outdata;
//outdata = rotateYUV420Degree180(data, width, height); //进行180旋转
//outdata = rotateYUV420Degree90(data, width, height); //进行90旋转
//outdata = rotateYUV420Degree270(data, width, height); //进行270旋转
//如果保存数据帧 需要将nv21进行转换
YuvImage yuvImage = new YuvImage(outdata, camera.getParameters()
.getPreviewFormat(), width, height, null);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 80,
byteArrayOutputStream);
byte[] jpegData = byteArrayOutputStream.toByteArray();
File file = new File("/sdcard"+ "/"+i+".png");
i++;
FileOutputStream fos = new FileOutputStream(file);
fos.write(jpegData, 0, jpegData.length);
fos.flush();
fos.close();
}
不同的android手机 ,可能存在多种情况,比如预览正常,保存的数据帧出现了90度 180度 270度旋转。
对NV21进行顺时针旋转90度,180度和270度算法。
旋转90度
privatebyte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight){
byte[] yuv =newbyte[imageWidth*imageHeight*3/2];
// Rotate the Y luma
int i =0;
for(int x =0;x < imageWidth;x++){
for(int y = imageHeight-1;y >=0;y--){
yuv[i]= data[y*imageWidth+x];
i++;}
}
// Rotate the U and V color components
i = imageWidth*imageHeight*3/2-1;for(int x = imageWidth-1;x >0;x=x-2){for(int y =0;y < imageHeight/2;y++){
yuv[i]= data[(imageWidth*imageHeight)+(y*imageWidth)+x];
i--;
yuv[i]= data[(imageWidth*imageHeight)+(y*imageWidth)+(x-1)];
i--;}}return yuv;}
用法:
//clockwise90:IplImage.create(480, 640) && new NewFFmpegFrameRecorder(480, 640)顺时针旋转90度,
将IplImage.create和new NewFFmpegFrameRecorder处源图像的宽高640x480对换成旋转后的真实宽高480x640
byte[] outdata;
outdata = rotateYUV420Degree90(data, 640, 480);
旋转180度
privatebyte[] rotateYUV420Degree180(byte[] data, int imageWidth, int imageHeight){
byte[] yuv =newbyte[imageWidth*imageHeight*3/2];
int i =0;int count =0;
for(i = imageWidth * imageHeight -1; i >=0; i--){
yuv[count]= data[i];
count++;}
i = imageWidth * imageHeight *3/2-1;for(i = imageWidth * imageHeight *3/2-1; i >= imageWidth
* imageHeight; i -=2){
yuv[count++]= data[i -1];
yuv[count++]= data[i];}return yuv;}
用法:
//clockwise180:IplImage.create(640, 480) && new NewFFmpegFrameRecorder(640, 480)上述2处无需改动
byte[] outdata;
outdata = rotateYUV420Degree180(data, 640, 480);
旋转270度
private byte[] rotateYUV420Degree270(byte[] data, int imageWidth, int imageHeight){
byte[] yuv =new byte[imageWidth*imageHeight*3/2];
// Rotate the Y luma
int i =0;
for(int x = imageWidth-1;x >=0;x--){
for(int y =0;y < imageHeight;y++){
yuv[i]= data[y*imageWidth+x];
i++;
}
}// Rotate the U and V color components
i = imageWidth*imageHeight;
for(int x = imageWidth-1;x >0;x=x-2){
for(int y =0;y < imageHeight/2;y++){
yuv[i]= data[(imageWidth*imageHeight)+(y*imageWidth)+(x-1)];
i++;
yuv[i]= data[(imageWidth*imageHeight)+(y*imageWidth)+x];
i++;
}
}
return yuv;
}
用法:
//clockwise270:IplImage.create(480, 640) && new NewFFmpegFrameRecorder(480, 640),设置与旋转90度相同
byte[] outdata;
outdata = rotateYUV420Degree270(data, 640, 480);
裁剪NV21
publicbyte[] cropYUV420(byte[] data,int imageW,int imageH,int newImageH){int cropH;int i,j,count,tmp;byte[] yuv =newbyte[imageW*newImageH*3/2];
cropH =(imageH - newImageH)/2;
count =0;for(j=cropH;j<cropH+newImageH;j++){for(i=0;i<imageW;i++){
yuv[count++]= data[j*imageW+i];}}
//Cr Cb
tmp = imageH+cropH/2;for(j=tmp;j<tmp + newImageH/2;j++){for(i=0;i<imageW;i++){
yuv[count++]= data[j*imageW+i];}}
return yuv;}
用法:
将640x480裁剪成480x480时用法如下:
在onPreviewFrame(byte[] data, Camera camera)中调用
byte[] outdata2;
byte[] outdata;
outdata2 = rotateYUV420Degree90(data, 640, 480);//将640x480旋转成480x640
outdata = cropYUV420(outdata2, 480, 640,480);//将480x640裁剪成480x480
在initVideoRecorder中
videoRecorder = new NewFFmpegFrameRecorder(strVideoPath, 480, 480, 1);
在handleSurfaceChanged中
yuvIplImage = IplImage.create(480, 480, IPL_DEPTH_8U, 2);
————————————————
版权声明:本文为CSDN博主「github.com/starRTC」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/elesos/article/details/53220309
这样 就可以获取到数据帧并完成旋转保存或者裁剪保存。
不要尝试将文件保存到本地之后,再对照片进行读取旋转,本人尝试过通过
android.graphics.Matrix matrix = new android.graphics.Matrix(); matrix.setRotate(degrees, bitmap.getWidth() / 2, bitmap.getHeight() / 2); Bitmap bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); bitmap.recycle();
方式旋转,但是Bitmap 是获取不到的。而且此方式不是常规做法
方式2、 使用照相机拍照功能
//照相机拍照后的回调
private final Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(final byte[] data, Camera camera) {
System.out.println(Thread.currentThread());
new Thread(new Runnable() {
@Override
public void run() {
File file = new File("/sdcard"+ "/facenew"+i+".png");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(data, 0, data.length);
fos.flush();
fos.close();
i++;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
camera.startPreview();//重新开始预览
}
};
@Override
public void onPreviewFrame(final byte[] imgData, final Camera camera) {
camera.takePicture(null, null, mPictureCallback);//执行拍照功能,拍照成功后执行回调
}
网上有网友提到 预览是正向的,保存照片是旋转的,可以按照方式1进行照片旋转。
两种方式的对比:
相机拍照功能 照片出现模糊的概率会小很多,但是每次执行takePicture的时候会推出预览。保存之后重新开始预览,会导致页面出现顿卡。
直接保存数据帧流畅度会好很多,不过照片的清晰度不如拍照的好 ,(实测)原因不清楚