Android Camera的使用(一)

在Api21中Camera类被废弃,取而代之的是camera2包。相对来说,camera2比Camera使用起来看似复杂了好多,但是在灵活性方面增加了很多。不过出于兼容性的考虑,加上当前Android设备5.0以下的占比还是比较大的,所以在相机开发的过程中,Camera类还是不得不掌握的。在本文中,讲解的是Camera的基本使用。而关于camera2的使用,在以后的文章中会单独再讲。

拍照步骤

  1. 添加相机相关权限
  2. 通过Camera.open(int)获得一个相机实例
  3. 利用camera.getParameters()得到相机实例的默认设置Camera.Parameters
  4. 如果需要的话,修改Camera.Parameters并调用camera.setParameters(Camera.Parameters)来修改相机设置
  5. 调用camera.setDisplayOrientation(int)来设置正确的预览方向
  6. 调用camera.setPreviewDisplay(SurfaceHolder)来设置预览,如果没有这一步,相机是无法开始预览的
  7. 调用camera.startPreview()来开启预览,对于拍照,这一步是必须的
  8. 在需要的时候调用camera.takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)来拍摄照片
  9. 拍摄照片后,相机会停止预览,如果需要再次拍摄多张照片,需要再次调用camera.startPreview()来重新开始预览
  10. 调用camera.stopPreview()来停止预览
  11. 一定要在onPause()的时候调用camera.release()来释放camera,在onResume中重新开始camera

相机权限

使用相机进行拍照,需要添加以下权限:

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

拍摄的照片需要存储到内存卡的话,还需要内存卡读写的权限:

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

拍照注意事项

根据拍照步骤就可以使用相机进行拍照,但是在使用过程中,有诸多需要注意的地方。

开启相机

开启相机直接调用Camera.open(),理论上就可以得到一个相机实例。但是在开启相机前,最好check一下。虽然目前基本上所有的手机都是前后摄像头,但是也不排斥有些奇葩的手机,只有后摄像头,或者干脆没有摄像头的。所有在open一个camera前,先检查是否存在该id的camera。Camera.getNumberOfCameras()可以获得当前设备的Camera的个数N,N为0表示不支持摄像头,否则对应的Camera的ID就是0—(N-1)。

相机设置

Camera的Parameters提供了诸多属性,可以对相机进行多种设置,包括预览大小及格式、照片大小及格式、预览频率、拍摄场景、颜色效果、对焦方式等等,具体设置可参考官方手册。
拍照尤其需要注意的是对预览大小、照片大小以及对焦方式的设置。
在对预览大小、照片大小及对焦方式设置时,设置的值必须是当前设备所支持的。否则,预览大小和照片大小设置会无效,对焦方式设置会导致崩溃。它们都有相应的方法,获取相应的支持的列表。对应的依次为getSupportedPictureSizes()getSupportedPictureSizes()getSupportedFocusModes()

相机预览方向

不对相机预览方向和应用方向设置,通常情况下得到的预览结果是无法接受的。一般应用设置的方向为固定竖向,预览设置旋转90度即可。严谨点来说,预览方向的设置是根据当前window的rotation来设置的,即((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation()的值。在Surface.ROTATION_0Surface.ROTATION_180时,Camera设置displayOrientation为90,否则设置为0。

预览View的设置

相机预览前,必须调用camera.setPreviewDisplay(SurfaceHolder)来设置预览的承载。SurfaceHolder一般取自SurfaceView、SurfaceTexture、TextureView等。一般情况下,如果不对显示的View大小做合理的设置,预览中的场景都会被变形。
如何保证预览不变形呢?预览效果变形是因为设置的previewSize和预览View的长宽比例不同造成的,将预览View的长宽比设置的与Camera的previewSize相同(需要注意的是,竖屏下是预览View的长宽比,要设置的与Camera的previewSize的宽长比相同)即可解决变形的问题。
以全屏预览为例,假如手机屏幕分辨率为1280*720,相机支持的预览大小没有1280*720的,也没有这个比例的,但是有1280*960的,相机预览大小选的也是这个。这时候,将预览View的大小设置为1280*960即可,超出屏幕的部分不予理会。值得注意的是,如果预览View的父布局是RelativeLayout,设置宽度大于父布局是无效的。可以重写RelativeLayout的onMeasure来实现,或者将父布局改为FrameLayout。

拍照监听及图片处理

相机拍照时在预览时,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)(或其重载方法)来实现拍照的,其中第一个参数表示图像捕获时刻的回调。可以看到此方法的后三个参数类型是一样的,都是图像回调。分别表示原始图数据回调、展示图像数据的回调、JPEG图像数据的回调。图像回调中得到byte数组decode为image后,图片的方向都是摄像头原始的图像方向。
可以通过parameters.setRotation(int)来改变最后一个回调中图像数据的方向。个人推荐不设置,直接在回调中利用矩阵变换统一处理。因为利用parameters.setRotation(int)来旋转图像,在不同手机上会有差异。
与预览View设置类似,pictureSize设置的值,影响了最后的拍照结果,处理时需要对拍照的结果进行裁剪,使图片结果和在可视区域预览的结果相同。前摄像头拍摄的结果还需要做对称变换,以保证“所见即所得”。

拍照示例

首先需要一个相机的控制类CameraKitKat,继承自ACamera,ACamera是一个抽象类,具有open(int),close()方法,控制相机的开启和关闭。这样做主要是为了后面扩展使用Camera2。CameraKitKat源码如下:

public class CameraKitKat extends ACamera{

    private Camera camera;
    private SurfaceHolder holder;
    private float displayScale;

    public CameraKitKat(SurfaceView surfaceView) {
        super(surfaceView);
        init();
    }

    private void init(){
        holder=displayView.getHolder();
    }

    @Override
    public void open(int type){
        int rotation=((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE))
                .getDefaultDisplay().getRotation();
        if(!openCamera(type))return;
        setParameters(camera,rotation);
        setDisplayOrientation(camera,rotation);
        setPreviewDisplay(camera,holder);
        camera.startPreview();
    }

    @Override
    public void close(){
        camera.stopPreview();
        camera.release();
    }

    //调整SurfaceView的大小
    private void resizeDisplayView(){
        Camera.Parameters parameters=camera.getParameters();
        Camera.Size size=parameters.getPreviewSize();
        FrameLayout.LayoutParams p= (FrameLayout.LayoutParams) displayView.getLayoutParams();
        float scale=size.width/(float)size.height;
        displayScale=displayView.getHeight()/(float)displayView.getWidth();
        if(scale>displayScale){
            p.height= (int) (scale*displayView.getWidth());
            p.width=displayView.getWidth();
        }else{
            p.width= (int) (displayView.getHeight()/scale);
            p.height=displayView.getHeight();
        }
        Log.e("wuwang","-->"+size.width+"/"+size.height);
        Log.e("wuwang","--<"+p.height+"/"+p.width);
        displayView.setLayoutParams(p);
        displayView.invalidate();
    }

    private boolean checkCameraId(int cameraId){
        return cameraId>=0&&cameraId//相机使用第一步,打开相机,获得相机实例
    private boolean openCamera(int cameraId){
        if(!checkCameraId(cameraId))return false;
        camera=Camera.open(cameraId);
        return true;
    }

    //相机使用第二步,设置相机实例参数
    //TODO :里面还存在问题,需要修改
    private void setParameters(Camera camera,int rotation){
        Camera.Parameters parameters=camera.getParameters();

        //PreviewSize设置为设备支持的最高分辨率
        final Camera.Size size=Collections.max(camera.getParameters().getSupportedPreviewSizes(),new Comparator() {
            @Override
            public int compare(Camera.Size lhs, Camera.Size rhs) {
                return lhs.width*lhs.height-rhs.width*rhs.height;
            }
        });
        parameters.setPreviewSize(size.width,size.height);

        //PictureSize设置为和预览大小最近的
        Camera.Size picSize=Collections.max(parameters.getSupportedPictureSizes(), new Comparator() {
            @Override
            public int compare(Camera.Size lhs, Camera.Size rhs) {
                return (int) (Math.sqrt(Math.pow(size.width-rhs.width,2)+Math.pow(size.height-rhs.height,2))-
                        Math.sqrt(Math.pow(size.width-lhs.width,2)+Math.pow(size.height-lhs.height,2)));
            }
        });
        parameters.setPictureSize(picSize.width,picSize.height);
        //如果相机支持自动聚焦,则设置相机自动聚焦,否则不设置
        if(parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)){
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        //设置颜色效果
//        parameters.setColorEffect(Camera.Parameters.EFFECT_MONO);

        camera.setParameters(parameters);
        resizeDisplayView();
    }

    //相机使用第三步,设置相机预览方向
    private void setDisplayOrientation(Camera camera,int rotation){
        if(rotation== Surface.ROTATION_0||rotation==Surface.ROTATION_180){
            camera.setDisplayOrientation(90);
        }else{
            camera.setDisplayOrientation(0);
        }
    }

    //相机使用第四步,设置相机预览载体SurfaceHolder
    private void setPreviewDisplay(Camera camera,SurfaceHolder holder){
        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void measureSize(int width, int height) {
        super.measureSize(width, height);
    }

    @Override
    public void takePicture() {
        super.takePicture();
        camera.takePicture(new Camera.ShutterCallback() {
            @Override
            public void onShutter() {

            }
        }, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {

            }
        }, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                if(pictureCallback!=null){
                    pictureCallback.onPictureTaken(data,displayScale);
                }
            }
        });
    }
}

本例中,使用SurfaceView作为预览View,提供SurfaceHolder给Camera。考虑到预览变形问题,使用FrameView作为其父布局。CameraPreview源码如下:

public class CameraPreview extends FrameLayout implements SurfaceHolder.Callback{

    private SurfaceView surfaceView;
    private ACamera camera;

    private boolean isCameraBack=false;

    public CameraPreview(Context context) {
        this(context,null);
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        addPreview();
        //后续增加CameraLollipop,根据系统版本使用Camera或者Camera2
        camera=new CameraKitKat(surfaceView);
        setKeepScreenOn(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        camera.measureSize(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void addPreview(){
        surfaceView=new SurfaceView(getContext());
        surfaceView.getHolder().addCallback(this);
        this.addView(surfaceView);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        camera.open(isCameraBack?0:1);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        setKeepScreenOn(false);
        camera.close();
    }

    public boolean isCameraBack(){
        return isCameraBack;
    }

    public void setOnPictureCallback(PictureCallback pictureCallback){
        camera.setOnPictureCallback(pictureCallback);
    }

    public void takePicture(){
        camera.takePicture();
    }
}

在拍照的Activity中,置入CameraPreview,长宽都设置为match_parent,然后增加一个拍照的按钮。增加拍照监听,并在监听中对图像数据进行处理即可。Activity的源码如下:

public class MainActivity extends Activity implements View.OnClickListener{

    private View btnTake;
    private ImageView ivShower;
    private CameraPreview cameraPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btnTake).setOnClickListener(this);
        ivShower= (ImageView) findViewById(R.id.ivShower);
        cameraPreview= (CameraPreview) findViewById(R.id.cameraView);
        cameraPreview.setOnPictureCallback(new PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data,float scale) {
                ivShower.setVisibility(View.VISIBLE);
                Bitmap bitmap=BitmapFactory.decodeByteArray(data,0,data.length);
                Bitmap bitmap2=rotateAndCropBitmap(bitmap,cameraPreview.isCameraBack()?90:-90,scale);
                saveBitmapToPath(bitmap2,Environment.getExternalStorageDirectory()+"/temp.jpeg");
                ivShower.setImageBitmap(bitmap2);
                bitmap.recycle();
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        File file=new File(Environment.getExternalStorageDirectory()+"/temp.jpeg");
        if(file.exists()){
            file.delete();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btnTake:
                cameraPreview.takePicture();
                break;
            default:
                break;
        }
    }

    private Bitmap rotateAndCropBitmap(Bitmap bm,int orientationDegree,float rate){
        //TODO : 貌似有些问题
        int width,height;
        float bmScale=bm.getHeight()/(float)bm.getWidth();
        if(rate==bmScale)return bm;
        else if(rate>bmScale){
            width=bm.getWidth();
            height= (int) (width/rate);
        }else{
            height= bm.getHeight();
            width= (int) (height*rate);
        }
        Matrix m = new Matrix();
        if(orientationDegree==-90){ //前摄像头,则左右镜像
            m.postScale(1,-1);
        }
        m.postRotate(orientationDegree);
        return Bitmap.createBitmap(bm,0,bm.getHeight()-height,width,height,m,true);
    }

    private void saveBitmapToPath(Bitmap bitmap,String path){
        File file=new File(path);
        try {
            FileOutputStream fos=new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG,90,fos);
            fos.flush();
            fos.close();
            Log.e("wuwang","filePath-->"+file.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(Android,午王)