Android Camera

转载: 实现自己的Camera

Android自带的Camera应用虽然可以满足大多数情景,但是其灵活性上还有不足。但是Android允许我们定制自己的Camera。

在Android的hardware包中有一个Camera类。这个类就是获取Camera服务的,可以定制Camera等。

可以通过open()方法获取其实例。

在使用这个类是需要在AndroidManifest.xml文件中加入相应的权限和特性

如:

  <uses-permission android:name = "android.permission.CAMERA" /> 
  <uses-feature android:name = "android.hardware.camera" /> 
  <uses-feature android:name = "android.hardware.camera.autofocus" />

本文实例:

package demo.camera;  
import java.io.OutputStream;  
import java.util.Iterator;  
import java.util.List;  
import android.app.Activity;  
import android.content.ContentValues;  
import android.content.res.Configuration;  
import android.hardware.Camera;  
import android.net.Uri;  
import android.os.Bundle;  
import android.provider.MediaStore;  
import android.view.SurfaceHolder;  
import android.view.SurfaceView;  
import android.view.View;  
import android.widget.LinearLayout;  
/** 
 * Android自带的Camera应用程序可以完成很多功能。但是当其不能满足我们需要的时候 
 * 我们可以定制自己的Camera。Android提供了Camera类来辅助我们实现自己的Camera。 
 * 这个例子就来定义一个自己的Camera 
 * 首先,在Manifest中需要引入权限<uses-permission android:name="android:permission.CAMERA"/> 
 * 我们需要用来存放取景器的容器,这个容器就是SurfaceView。 
 * 使用SurfaceView的同时,我们还需要使用到SurfaceHolder,SurfaceHolder相当于一个监听器,可以监听 
 * Surface上的变化,通过其内部类CallBack来实现。 
 * 为了可以获取图片,我们需要使用Camera的takePicture方法同时我们需要实现Camera.PictureCallBack类,实现onPictureTaken方法 
 * @author Administrator 
 * 
 */  
public class MyCamera extends Activity implements SurfaceHolder.Callback,Camera.PictureCallback{  
      
    public static final int MAX_WIDTH = 200;  
    public static final int MAX_HEIGHT = 200;  
      
    private SurfaceView surfaceView;  
      
    private Camera camera; //这个是hardare的Camera对象  
      
    public void onCreate(Bundle savedInstanceState){  
        super.onCreate(savedInstanceState);  
        this.setContentView(R.layout.camera);  
        surfaceView = (SurfaceView)this.findViewById(R.id.myCameraView);  
        surfaceView.setFocusable(true);   
        surfaceView.setFocusableInTouchMode(true);  
        surfaceView.setClickable(true);  
        surfaceView.setOnClickListener(new View.OnClickListener() {  
              
            @Override  
            public void onClick(View v) {  
                  
                camera.takePicture(null, null, null, MyCamera.this);  
                  
            }  
        });  
        //SurfaceView中的getHolder方法可以获取到一个SurfaceHolder实例  
        SurfaceHolder holder = surfaceView.getHolder();  
        //为了实现照片预览功能,需要将SurfaceHolder的类型设置为PUSH  
        //这样,画图缓存就由Camera类来管理,画图缓存是独立于Surface的  
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);  
        holder.addCallback(this);  
    }  
    @Override  
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {  
    }  
    @Override  
    public void surfaceCreated(SurfaceHolder holder) {  
        // 当Surface被创建的时候,该方法被调用,可以在这里实例化Camera对象  
        //同时可以对Camera进行定制  
        camera = Camera.open(); //获取Camera实例  
      
          
        /** 
         * Camera对象中含有一个内部类Camera.Parameters.该类可以对Camera的特性进行定制 
         * 在Parameters中设置完成后,需要调用Camera.setParameters()方法,相应的设置才会生效 
         * 由于不同的设备,Camera的特性是不同的,所以在设置时,需要首先判断设备对应的特性,再加以设置 
         * 比如在调用setEffects之前最好先调用getSupportedColorEffects。如果设备不支持颜色特性,那么该方法将 
         * 返回一个null 
         */  
        try {  
              
            Camera.Parameters param = camera.getParameters();  
            if(this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE){  
                //如果是竖屏  
                param.set("orientation", "portrait");  
                //在2.2以上可以使用  
                //camera.setDisplayOrientation(90);  
            }else{  
                param.set("orientation", "landscape");  
                //在2.2以上可以使用  
                //camera.setDisplayOrientation(0);                
            }  
            //首先获取系统设备支持的所有颜色特效,有复合我们的,则设置;否则不设置  
            List<String> colorEffects = param.getSupportedColorEffects();  
            Iterator<String> colorItor = colorEffects.iterator();  
            while(colorItor.hasNext()){  
                String currColor = colorItor.next();  
                if(currColor.equals(Camera.Parameters.EFFECT_SOLARIZE)){  
                    param.setColorEffect(Camera.Parameters.EFFECT_SOLARIZE);  
                    break;  
                }  
            }  
            //设置完成需要再次调用setParameter方法才能生效  
            camera.setParameters(param);  
              
            camera.setPreviewDisplay(holder);  
              
            /** 
             * 在显示了预览后,我们有时候希望限制预览的Size 
             * 我们并不是自己指定一个SIze而是指定一个Size,然后 
             * 获取系统支持的SIZE,然后选择一个比指定SIZE小且最接近所指定SIZE的一个 
             * Camera.Size对象就是该SIZE。 
             *  
             */  
            int bestWidth = 0;  
            int bestHeight = 0;  
              
            List<Camera.Size> sizeList = param.getSupportedPreviewSizes();  
            //如果sizeList只有一个我们也没有必要做什么了,因为就他一个别无选择  
            if(sizeList.size() > 1){  
                Iterator<Camera.Size> itor = sizeList.iterator();  
                while(itor.hasNext()){  
                    Camera.Size cur = itor.next();  
                    if(cur.width > bestWidth && cur.height>bestHeight && cur.width <MAX_WIDTH && cur.height < MAX_HEIGHT){  
                        bestWidth = cur.width;  
                        bestHeight = cur.height;  
                    }  
                }  
                if(bestWidth != 0 && bestHeight != 0){  
                    param.setPreviewSize(bestWidth, bestHeight);  
                    //这里改变了SIze后,我们还要告诉SurfaceView,否则,Surface将不会改变大小,进入Camera的图像将质量很差  
                    surfaceView.setLayoutParams(new LinearLayout.LayoutParams(bestWidth, bestHeight));  
                }  
            }  
            camera.setParameters(param);  
        } catch (Exception e) {  
            // 如果出现异常,则释放Camera对象  
            camera.release();  
        }  
          
        //启动预览功能  
        camera.startPreview();  
          
    }  
    @Override  
    public void surfaceDestroyed(SurfaceHolder holder) {  
        // 当Surface被销毁的时候,该方法被调用  
        //在这里需要释放Camera资源  
        camera.stopPreview();  
        camera.release();  
          
    }  
    @Override  
    public void onPictureTaken(byte[] data, Camera camera) {  
        // data是一个原始的JPEG图像数据,  
        //在这里我们可以存储图片,很显然可以采用MediaStore  
        //注意保存图片后,再次调用startPreview()回到预览  
        Uri imageUri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());  
        try {  
            OutputStream os = this.getContentResolver().openOutputStream(imageUri);  
            os.write(data);  
            os.flush();  
            os.close();  
        } catch (Exception e) {  
            // TODO: handle exception  
            e.printStackTrace();  
        }  
          
        camera.startPreview();  
    }  
      
}  

精简实例:

import java.io.IOException;
import java.util.List;

import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class CameraView extends SurfaceView implements SurfaceHolder.Callback {
	private final static String TAG = "CameraView";
	private static int DEF_DISPLAY_ROTATION = 90; // degree
	
	private SurfaceHolder mHolder;
	private Camera mCamera;
	
	private Size mPreviewSize;
	private List<Size> mSupportedPreviewSizes;
	
	private byte[] mPreviewData = new byte[640*480*3/2];
	
	public CameraView(Context context, AttributeSet set) {
		super(context, set);
		mCamera = null;
		mHolder = this.getHolder();
		mHolder.addCallback(this);
	}
	
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mHolder = holder;
		
		try {
			mCamera = Camera.open(1);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		if (mCamera != null) {
			mCamera.addCallbackBuffer(mPreviewData);
			mCamera.setPreviewCallbackWithBuffer(mPreviewCallback);
			this.requestLayout();
		}
	}
	
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
		if (null == holder.getSurface()) {
			return;
		}
		mHolder = holder;
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		mCamera.stopPreview();
		mCamera.setPreviewCallbackWithBuffer(null);
		mCamera.release();
		mCamera = null;
		mHolder = null;
	}
	
	public void setCameraDisplayOrientation(Camera camera, int degree) {
		try {
			camera.setDisplayOrientation(degree);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (mCamera == null) {
			return;
		}
		int width = r - l;
		int height = b - t;
		mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
		if (mSupportedPreviewSizes != null) {
			mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
			if (null == mPreviewSize || null == mCamera || null == mHolder) {
				return;
			}
			setCameraParam();
			try {
				mCamera.setPreviewDisplay(mHolder);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			setCameraDisplayOrientation(mCamera, DEF_DISPLAY_ROTATION);
			mCamera.startPreview();
		}
	}

	private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
		Size targetSize = null;
	
		double refRatio = (double) Math.min(w, h) / Math.max(w, h);
		double diff = 1;
		double tmpDiff = 0;
	
		for (Size size : sizes) {
			tmpDiff = Math.abs(refRatio - ((double) Math.min(size.width, size.height) / Math.max(size.width, size.height)));
			if (tmpDiff < diff) {
				diff = tmpDiff;
				targetSize = size;
			}
		}
	
		return targetSize;
	}
	
	private void setCameraParam() {
		Camera.Parameters param = mCamera.getParameters();
		param.setPreviewFormat(ImageFormat.NV21); 

		int range[] = new int[2];
		param.getPreviewFpsRange(range);
  
		// detect device type for s5
		if (android.os.Build.MODEL.equals("SM-G9006V")) {
		  param.setPreviewFpsRange(25000, range[1]);
		} else {
			param.setPreviewFpsRange(range[1], range[1]);
		}
  
  		Log.d(TAG, "camera fps: " + range[0] + ", " + range[1]);

		mPreviewSize.width = 640;
		mPreviewSize.height = 480;
		param.setPreviewSize(mPreviewSize.width, mPreviewSize.height);

		try {
			mCamera.setParameters(param);
		} catch (Exception e) {
			Log.d(TAG, "set camera parameter error:" + e);
			param.setPreviewFpsRange(range[0], range[1]);
			try {
				mCamera.setParameters(param);
			} catch (Exception e2) {
				Log.e(TAG, "set camera parameter error:" + e2);
			}
		}
	}

	private PreviewCallback mPreviewCallback = new PreviewCallback() {
		
		@Override
		public void onPreviewFrame(byte[] data, Camera camera) {
			if (null != data) {
				//process NV21 pixel format image here
				
				mCamera.addCallbackBuffer(data);
			}
		}
		
		private void turnRotation(int width, int height, byte[] src, byte[] dest) {
			final int SUM = width * height;
			int h = SUM;

			// Y
			for (int i = 0; i < width; i++) {
				for (int j = 0; j < height; j++) {
					dest[--h] = src[j * width + i];
				}
			}

			h = SUM * 3 / 2;
			// UV
			for (int i = 0; i < width; i += 2) {
				for (int j = 0; j < height / 2; j++) {
					dest[--h] = src[SUM + j * width + i + 1];
					dest[--h] = src[SUM + j * width + i];
				}
			}
		}

		private void turnRotationBack(int width, int height, byte[] src, byte[] dest) {
			final int SUM = width * height;
			int h = SUM;
			
			// Y
			for (int i = 0; i < width; i++) {
				for (int j = 0; j < height; j++) {
				       dest[j * width + i] = src[--h];
				}
			}
			
			h = SUM * 3 / 2;
			int x = SUM * 3 / 2;
			
			// UV
			for (int i = 0; i < width; i += 2) {
				for (int j = 0; j < height / 2; j++) {
				       dest[--x] = src[--h];
				       dest[--x] = src[--h];
				}
			}
		}
	};
}

补充1:NV21(yuv420sp)旋转90度

参考网址: yuv420sp旋转90度

  1. 对比YUV420sp和YUV420p数据格式

  2. Android摄像头采集的YUV420sp数据,旋转90度的算法

具体旋转算法待详细总结(包括顺时针和逆时针旋转90度)。。。

补充2:NV21转Bitmap

参考网址: Android摄像头开发:实时摄像头视频预览帧的编码问题(二)

...
camera.addCallbackBuffer(...);
camera.setPreviewCallbackWithBuffer(callback);
...
private PreviewCallback callback = new PreviewCallback() {
  public void onPreviewFrame(byte[] mData, Camera camera) {
  ...
  final YuvImage image = new YuvImage(mData, ImageFormat.NV21, w, h, null);  
  ByteArrayOutputStream os = new ByteArrayOutputStream(mData.length);  
  if(!image.compressToJpeg(new Rect(0, 0, w, h), 100, os)){  
    return null;  
  }  
  byte[] tmp = os.toByteArray();  
  Bitmap bmp = BitmapFactory.decodeByteArray(tmp, 0, tmp.length);   
  ...
  }
}

具体细节有待总结。。。

相关网址:

  1. Android Camera(摄像头)开发实例

  2. android摄像头获取图像

  3. Android Camera Api的心得

  4. android ACTION_VIDEO_CAPTURE 返回null

  5. Android自定义照相机 预览拍照 切换前后置摄像头