android 攝像頭保存照片兩種方式

方式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的時候會推出預覽。保存之後重新開始預覽,會導致頁面出現頓卡。

直接保存數據幀流暢度會好很多,不過照片的清晰度不如拍照的好 ,(實測)原因不清楚

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章