hardware.Camera及SurfaceView

SufaceView

        主要有Surface,SurfaceView及SurfaceHolder三个类。Surface对象代表着原始像素数据的缓冲区.SurfaceHolder是与Surface联系的纽带,而SurfaceView内部封装了一个专门用于绘制的Surface对象。(Provides a dedicated drawing surface embedded inside of a view hierarchy)。
        Surface对象也是有生命周期的,当SurfaceView出现在屏幕上时会创建Surface,当SurfaceView从屏幕上消失时,Surface会销毁。为了正确把握Surface的生命周期,可以通过SurfaceHolder.Callback接口实现。

        SurfaceView是View的特殊子类,用于在视图层级结构中提供一个专门用来绘图的Surface,其目的是将这个绘图Surface提供给另一个线程(非UI线程),这样系统就不必等到视图层级结构做好绘制准备后再进行绘制。相反,另一个线程执有Surface的引用,它可以根据自己的步调向自身的canvas中进行绘制  。

        我们向应用程序添加的UI组件或者自定义控件,都会被加入到视图层级结构中,完整的视图树是在UI线程中进行绘制的。而SurfaceView是在自己的线程中进行绘制的,并不会用到UI线程

        SurfaceView与其他View不相同的地方在于,它不会进行自我绘制内容。把想将内容绘制到Surface缓冲区的对象,叫做Surface的客户端。

SurfaceHolder.Callback

里面有三个方法,分别为:
        surfaceCreated(SurfaceHolder):SurfaceView被放置到屏幕上时调用该方法,这里也是Surface与其客户端进行关联的地方。
        surfaceChanged(SurfaceHolder , int ,int , int):当Surface的格式和尺寸改变时都会立刻调用该方法,而且该方法至少肯定会被调用一次(在surfaceCreated()调用之后)。这里通常用来通知Surface的客户端有多大的区域可以使用。
        surfaceDestroyed(SurfaceHolder):SurfaceView从屏幕上移动,Surface被销毁时。在该方法中可以通知客户端停止使用Surface。

Camera

启动系统相机

    /**
     * 打开相机,将图片存储到指定的文件处
     */
    public static void openCamera(Activity activity, int requestCode, File file) {
        if (file == null) {
            return;
        }
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
        if (requestCode != 0) {
            activity.startActivityForResult(intent, requestCode);
        } else {
            activity.startActivity(intent);
        }
    }
        如果将上面的隐匿intent的action设置为MediaStore.ACTION_VID E O_CAPTURE就会启动录像功能。
        启用系统相机之后,所拍照的图片没办法立即呈现在图库中,需要通知系统进行刷新。如下:
                        //通知系统扫描
                        MediaScannerConnection.scanFile(this, new String[]{AppUtils.getCaptureImageFile(this).getAbsolutePath()}, null, null);
                        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(captureImageFile)));
        其中captureImageFile是用来存储拍照的图片,而AppUtils.getCaptureImageFile()返回的是图片所在的文件夹。也就是说该方法只能刷新指定的文件,而不是刷新一个文件夹。

概述       

        Camera提供了对设备相机硬件级别的调用。相机是一种独占性资源,一次只能有一个activity调用相机,因此使用相机很关键的一点:需要时使用,使用完毕之后立即释放。如果忘记释放,除非重启设备,否则其他应用将无法使用相机。

权限

        如果使用相机,需要在清单文件中添加相应的权限及<uses-feature>。<uses-feature>是用来指定应用使用了设备的某项特色功能,它可以保证只有那些具备了该功能的设备才能使用该应用。具体如下:

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

打开与释放

        在onResume()和onPause()回调方法中获取和释放相机资源。因为这两个方法是用户可以和视图交互的时间边界,只有用户可以与视图交互时才需要使用相机。获取和释放相机的代码如下:

	@Override
	protected void onResume() {//获取相机资源
		super.onResume();
		mCamera = Camera.open();
	}

	@Override
	protected void onPause() {//释放相机资源
		if (mCamera != null) {
			mCamera.release();
			mCamera = null;
		}
		super.onPause();
	}

        使用相机时必须进行非空判断,这是必须的。因为即使请求获取相机资源,也有可能获取不到。

常用方法

        open():打开后置摄像头。一个重载方法open(int),它是在api9中添加的,系统会根据传入的值打开对应的相机。一般来说,具体使用如下(open()源码):

    public static Camera open() {
        int numberOfCameras = getNumberOfCameras();
        CameraInfo cameraInfo = new CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
            getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
                return new Camera(i);
            }
        }
        return null;
    }
        遍历每一个摄像头,然后根据前后位置判断要打开哪个。

        setDisplayOrientation():设置预览方向。默认时预览为横屏的,调用setDisplayOrientation(90)后,才会变成竖屏的。

        getParameters():获取相机的参数。

        setParameters():这相机设置参数。

        autoFocus():自动对焦。参数为自动对焦后的回调。

        setPreviewCallback():设置预览的回调。在该加高中,可以将预览的数据保存。

判断是否有相机

具体代码如下:

	/**
	 * @return true代表有相机,false代表无相机
	 */
	private boolean hasCamera() {
		PackageManager pm = getPackageManager();
		return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
				|| pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)
				|| Camera.getNumberOfCameras() > 0;
	}

预览        

        在使用Camera拍照时需要预览界面,并且使用SurfaceView充当预览界面。具体代码如下:
		SurfaceView sv = (SurfaceView) findViewById(R.id.btn1);
		SurfaceHolder holder = sv.getHolder();
		holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		holder.addCallback(new Callback() {
			public void surfaceCreated(SurfaceHolder holder) {
				try {
					if (mCamera != null)//设置预览
						mCamera.setPreviewDisplay(holder);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			public void surfaceChanged(SurfaceHolder holder, int format,
					int width, int height) {
				if (mCamera == null)
					return;
				//设置相机的预览尺寸
				Parameters parameters = mCamera.getParameters();
				List<Size> sizes = parameters.getSupportedPreviewSizes();
				Size s = sizes.get(0);
				int ar = s.width * s.height;
				for (Size ts : sizes) {
					int ta = ts.width * ts.height;
					if (ta > ar) {
						s = ts;
						ar = ta;
					}
				}
				parameters.setPreviewSize(s.width, s.height);
				mCamera.setParameters(parameters);
				//开始预览。如果出异常,就要释放相机资源
				try {
					mCamera.startPreview();
				} catch (Exception e) {
					mCamera.release();
					mCamera = null;
				}
			}
			public void surfaceDestroyed(SurfaceHolder holder) {
				if (mCamera != null)
					mCamera.stopPreview();
			}
		});

        在设置预览之前,调用了SurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS),为了在低版本上运行这一点是必须有的。

        在上面代码中,在surfaceCreated()中调用Camera.setPreviewDisplay(),在surfaceChanged()中调用Camera.startPreview(),在surfaceDestroyed()中调用Camera.stopPreview()

获取预览数据

        主要通过setPreCallback()中的回调进行。如下:

                    camera.startPreview();
                    camera.setPreviewCallback(new Camera.PreviewCallback() {
                        @Override
                        public void onPreviewFrame(byte[] data, Camera camera) {
                            takePicture(data);
                        }
                    });
takePicture是将参数转为bitmap的方法,如下:
private void takePicture(byte[] data) {
        Camera.Size size = camera.getParameters().getPreviewSize();
        try {
            YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, stream);
            Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());//获取预览的bitmap
            //do sth
            stream.close();
        } catch (Exception ex) {
            Log.e("Sys", "Error:" + ex.getMessage());
        }
    }
        该方法主要是利用系统自带的YuvImage类进行。其中第二个参数只能是ImageFormat.NV21或者ImageFormat.YUY2。

获取合适的预览尺寸

代码来源于apidemo

    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;
        if (sizes == null)
            return null;
        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
        // Try to find an size match aspect ratio and size
        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                continue;
            if (Math.abs(size.height - h) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - h);
            }
        }

        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - h) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - h);
                }
            }
        }
        return optimalSize;
    }

拍照

        调用Camera.takePicture()进行拍照即可。它的第一个参数会在相机捕获图像时调用,此时图像数据还未处理完成;第二个参数会在原始图像数据可用时调用,此时原始图像数据处理完成,但还没有进行存储;第三个参数指的是JPEG版本的图像可用时。如果不需要相应的回调,可以直接传入null。具体如下:
				if (mCamera != null) {
					mCamera.takePicture(new ShutterCallback() {
						public void onShutter() {
							System.out.println("咔咔响");
						}
					}, null, new PictureCallback() {//将数据存储成JPG格式
						public void onPictureTaken(byte[] data, Camera camera) {
							File directory = Environment
									.getExternalStorageDirectory();
							File f = new File(directory, "me.jpg");
							FileOutputStream fo = null;
							try {
								fo = new FileOutputStream(f);
								fo.write(data);
							} catch (Exception e) {
								e.printStackTrace();
							} finally {
								if (fo != null) {
									try {
										fo.close();
									} catch (IOException e1) {
										e1.printStackTrace();
									}
								}
							}
							finish();
						}
					});
				}

Camera.Parameters

        为相机设置参数的类,通过Camera#getParameters()获取。

常用方法

        setPictureFormat():设置拍照后图片的格式。如:ImageFormat.JPEG等。
        setFocusMode():设置对焦模式。如Camera.Parameters.FOCUS_MODE_AUTO——自动对焦。

常见问题

拍摄指定位置的图像

现象

        使用Camera进行拍照,在预览图上有边框,拍摄的图片是边框中的部分。

原理

        Camera实现拍照,必须使用一个SurfaceView做为屏幕,用来显示预览图片。而边框可以使用另一个Surfaceview,两个SurfaceView采用帧布局。把底部的SurfaceView设置为Camera的预览区域,顶部是用来进行边框显示。

        这里需要自定义的MySurfaceView,并画出一个边框,代码实现为:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback{
	
	private SurfaceHolder holder;
	public MySurfaceView(Context context, AttributeSet attrs) {
		super(context, attrs);
		holder = getHolder();
		holder.addCallback(this);
		holder.setFormat(PixelFormat.TRANSPARENT);
		setZOrderOnTop(true);
	}
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		
	}
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		
	}
	public void draw(){
		Canvas canvas = holder.lockCanvas();
		canvas.drawColor(Color.TRANSPARENT);
		Paint p = new Paint();
		p.setStrokeWidth(4);
		p.setColor(Color.RED);
		p.setStyle(Paint.Style.STROKE);
		p.setAntiAlias(true);
		canvas.drawRect(100, 300, 400, 600, p);//在顶部画一个矩形,可以根据surfaceview的位置进行画
		holder.unlockCanvasAndPost(canvas);
	}
}
        最后在activity中将拍照获取的图片截取框中的部分即可。



你可能感兴趣的:(hardware.Camera及SurfaceView)