zoukankan      html  css  js  c++  java
  • 使用Camera进行拍照 & 后台静默拍照的思路

    Android中的Camera可以用来进行自定义相机、取景框实时预览、拍照等操作。在5.0中,这个类不推荐使用了,新出了一个Camera2,那个东西没怎么研究过,反正一时半会用不到。本篇讲解的是如果用这个对象进行拍照,最后在提及下如何进行后台的静默拍照。

    API翻译:http://bbs.51cto.com/thread-1063856-1.html(挺简单易懂的)

    一、CameraManager

    这个类是我自己封装的,不是API提供的。我在这里封装了打开相机,获取相机ID,保存拍照图片的操作。

    1.1 初始化

    初始化时需要传入一个camera对象,还有一个SurfaceHolder对象。传入Camera对象的目的是用于之后的拍照等操作,surfaceHloder是用于操作取景框预览图片的。

        private Camera mCamera;
        private SurfaceHolder mHolder;
    
        public CameraManager(Camera camera, SurfaceHolder holder) {
            mCamera = camera;
            mHolder = holder;
    
        }
    
        public Camera getCamera() {
            return mCamera;
        }

    1.2 打开相机

    这部分的步骤是,先找到Camera的id,然后通过Camera.open(id)打开这个id的摄像头(获取id的方法下面会说到),并且返回一个Camera对象,用于以后的操作。

    Camera android.hardware.Camera.open(int cameraId)

    如果开启失败,那么camera对象就是null,如果开启成功,那么就需要提供实时预览,让用户能看到摄像头中的东西。

    重要代码:

    mCamera.setPreviewDisplay(mHolder);
    // 如果成功开始实时预览
    mCamera.startPreview();

    代码片段:

        // 将摄像头中的图像展示到holder中
            try {
                // 这里的myCamera为已经初始化的Camera对象
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
                // 如果出错立刻进行处理,停止预览照片
                mCamera.stopPreview();
                mCamera.release();
                mCamera = null;
            }
            // 如果成功开始实时预览
            mCamera.startPreview();

    全部代码:

        /**
         * 打开相机
         * 
         * @param camera
         *            照相机对象
         * @param holder
         *            用于实时展示取景框内容的控件
         * @param tagInfo
         *            摄像头信息,分为前置/后置摄像头 Camera.CameraInfo.CAMERA_FACING_FRONT:前置
         *            Camera.CameraInfo.CAMERA_FACING_BACK:后置
         * @return 是否成功打开某个摄像头
         */
        public boolean openCamera(int tagInfo) {
            // 尝试开启摄像头
            try {
                mCamera = Camera.open(getCameraId(tagInfo));
            } catch (RuntimeException e) {
                e.printStackTrace();
                return false;
            }
            // 开启前置失败
            if (mCamera == null) {
                return false;
            }
            // 将摄像头中的图像展示到holder中
            try {
                // 这里的myCamera为已经初始化的Camera对象
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
                // 如果出错立刻进行处理,停止预览照片
                mCamera.stopPreview();
                mCamera.release();
                mCamera = null;
            }
            // 如果成功开始实时预览
            mCamera.startPreview();
            return true;
        }

    1.3 获取摄像头的id

    获取id的步骤是:先得到本机摄像头的数目,开始一个个判断前置/后置摄像头的id

    /**
         * @param tagInfo
         * @return 得到特定camera info的id
         */
        private int getCameraId(int tagInfo) {
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            // 开始遍历摄像头,得到camera info
            int cameraId, cameraCount;
            for (cameraId = 0, cameraCount = Camera.getNumberOfCameras(); cameraId < cameraCount; cameraId++) {
                Camera.getCameraInfo(cameraId, cameraInfo);
                if (cameraInfo.facing == tagInfo) {
                    break;
                }
            }
            return cameraId;
        }

    获取前置/后置摄像头的id

    通过传入不同的参数,就可以得到前置/后置的id了

        /**
         * @return 前置摄像头的ID
         */
        public int getFrontCameraId() {
            return getCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT);
        }
    
        /**
         * @return 后置摄像头的ID
         */
        public int getBackCameraId() {
            return getCameraId(Camera.CameraInfo.CAMERA_FACING_BACK);
        }

    1.4 设定拍照成功后照片保存的路径和照片名称

    首先要保证拍摄时存放照片的根目录存在,因为有时候用户会自己闲的蛋疼的删除掉你建立好的目录,拍照时你的程序找不到目录就会报错,一报错用户就觉得你的程序差劲,不好用。为了避免这个问题,我们每次保存前都需要判断下图片保存的目录是否存在。

            // 创建并保存图片文件
                File mFile = new File(PHOTO_PATH);
                if (!mFile.exists()) {
                    mFile.mkdirs();
                }

    图片的名字一般是根据当前时间来设定的,如果有其他需要请自行修改。

        /**
         * 定义图片保存的路径和图片的名字
         */
        public final static String PHOTO_PATH = "mnt/sdcard/CAMERA_DEMO/Camera/";
    
        public static String getPhotoFileName() {
            Date date = new Date(System.currentTimeMillis());
            SimpleDateFormat dateFormat = new SimpleDateFormat("'LOCK'_yyyyMMdd_HHmmss");
            return dateFormat.format(date) + ".jpg";
        }

    1.5 处理拍照的结果

    当拍照成功后会回调一个方法,在这个方法中我们就可以进行相片的处理了。由于android拍照后的照片和真实照片是垂直的,所以需要旋转处理。最后去保存到SD卡。在保存完毕后,请务必对camera进行收尾处理,释放资源。

        /**
         * 拍照成功回调
         */
        public class PicCallback implements PictureCallback {
            private String TAG = getClass().getSimpleName();
            private Camera mCamera;
    
            public PicCallback(Camera camera) {
                // TODO 自动生成的构造函数存根
                mCamera = camera;
            }
    
            /* 
             * 将拍照得到的字节转为bitmap,然后旋转,接着写入SD卡
             * @param data 
             * @param camera
             */
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                // 将得到的照片进行270°旋转,使其竖直
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                Matrix matrix = new Matrix();
                matrix.preRotate(270);
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                // 创建并保存图片文件
                File mFile = new File(PHOTO_PATH);
                if (!mFile.exists()) {
                    mFile.mkdirs();
                }
                File pictureFile = new File(PHOTO_PATH, getPhotoFileName());
                try {
                    FileOutputStream fos = new FileOutputStream(pictureFile);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                    bitmap.recycle();
                    fos.close();
                    Log.i(TAG, "拍摄成功!");
                } catch (Exception error) {
                    Log.e(TAG, "拍摄失败");
                    error.printStackTrace();
                } finally {
                    mCamera.stopPreview();
                    mCamera.release();
                    mCamera = null;
                }
            }
    
        }

    1.6 本类的全部代码

    package com.kale.camerademo;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Matrix;
    import android.hardware.Camera;
    import android.hardware.Camera.PictureCallback;
    import android.util.Log;
    import android.view.SurfaceHolder;
    
    public class CameraManager {
    
        private Camera mCamera;
        private SurfaceHolder mHolder;
    
        public CameraManager(Camera camera, SurfaceHolder holder) {
            mCamera = camera;
            mHolder = holder;
    
        }
    
        public Camera getCamera() {
            return mCamera;
        }
    
        /**
         * 打开相机
         * 
         * @param camera
         *            照相机对象
         * @param holder
         *            用于实时展示取景框内容的控件
         * @param tagInfo
         *            摄像头信息,分为前置/后置摄像头 Camera.CameraInfo.CAMERA_FACING_FRONT:前置
         *            Camera.CameraInfo.CAMERA_FACING_BACK:后置
         * @return 是否成功打开某个摄像头
         */
        public boolean openCamera(int tagInfo) {
            // 尝试开启摄像头
            try {
                mCamera = Camera.open(getCameraId(tagInfo));
            } catch (RuntimeException e) {
                e.printStackTrace();
                return false;
            }
            // 开启前置失败
            if (mCamera == null) {
                return false;
            }
            // 将摄像头中的图像展示到holder中
            try {
                // 这里的myCamera为已经初始化的Camera对象
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
                // 如果出错立刻进行处理,停止预览照片
                mCamera.stopPreview();
                mCamera.release();
                mCamera = null;
            }
            // 如果成功开始实时预览
            mCamera.startPreview();
            return true;
        }
    
        /**
         * @return 前置摄像头的ID
         */
        public int getFrontCameraId() {
            return getCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT);
        }
    
        /**
         * @return 后置摄像头的ID
         */
        public int getBackCameraId() {
            return getCameraId(Camera.CameraInfo.CAMERA_FACING_BACK);
        }
    
        /**
         * @param tagInfo
         * @return 得到特定camera info的id
         */
        private int getCameraId(int tagInfo) {
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            // 开始遍历摄像头,得到camera info
            int cameraId, cameraCount;
            for (cameraId = 0, cameraCount = Camera.getNumberOfCameras(); cameraId < cameraCount; cameraId++) {
                Camera.getCameraInfo(cameraId, cameraInfo);
    
                if (cameraInfo.facing == tagInfo) {
                    break;
                }
            }
            return cameraId;
        }
    
        /**
         * 定义图片保存的路径和图片的名字
         */
        public final static String PHOTO_PATH = "mnt/sdcard/CAMERA_DEMO/Camera/";
    
        public static String getPhotoFileName() {
            Date date = new Date(System.currentTimeMillis());
            SimpleDateFormat dateFormat = new SimpleDateFormat("'LOCK'_yyyyMMdd_HHmmss");
            return dateFormat.format(date) + ".jpg";
        }
        
        /**
         * 拍照成功回调
         */
        public class PicCallback implements PictureCallback {
            private String TAG = getClass().getSimpleName();
            private Camera mCamera;
    
            public PicCallback(Camera camera) {
                // TODO 自动生成的构造函数存根
                mCamera = camera;
            }
    
            /* 
             * 将拍照得到的字节转为bitmap,然后旋转,接着写入SD卡
             * @param data 
             * @param camera
             */
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                // 将得到的照片进行270°旋转,使其竖直
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                Matrix matrix = new Matrix();
                matrix.preRotate(270);
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                // 创建并保存图片文件
                File mFile = new File(PHOTO_PATH);
                if (!mFile.exists()) {
                    mFile.mkdirs();
                }
                File pictureFile = new File(PHOTO_PATH, getPhotoFileName());
                try {
                    FileOutputStream fos = new FileOutputStream(pictureFile);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                    bitmap.recycle();
                    fos.close();
                    Log.i(TAG, "拍摄成功!");
                } catch (Exception error) {
                    Log.e(TAG, "拍摄失败");
                    error.printStackTrace();
                } finally {
                    mCamera.stopPreview();
                    mCamera.release();
                    mCamera = null;
                }
            }
    
        }
    
    }

    二、布局文件

    在讲解java代码之前,先贴下布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <SurfaceView
            android:id="@+id/front_surfaceview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    
        <SurfaceView
            android:id="@+id/back_surfaceview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1" >
    
            <Button
                android:id="@+id/openFront_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_alignParentTop="true"
                 android:onClick="buttonListener"
                android:text="open front camera" />
    
            <Button
                android:id="@+id/openBack_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_alignParentTop="true"
                android:onClick="buttonListener"
                android:text="open back camera" />
    
        </RelativeLayout>
    
    </LinearLayout>

    这里主要是放了两个预览框,一个是预览前置的一个是预览后置的。最下面是两个按钮,用来照相。这里的行为是打开摄像头后自动照相,所以就用了open xxxx camera作名字。

    三、MainActivity

    为了便于讲解,这部分我只说下对前置的操作,后置的操作完全一致,就不赘述了。

    3.1 对象

        private CameraManager frontCameraManager;
    
        /**
         * 定义前置有关的参数
         */
        private SurfaceView frontSurfaceView;
        private SurfaceHolder frontHolder;
        private boolean isFrontOpened = false;
        private Camera mFrontCamera;    

    3.2 初始化对象

    先找到surfaceView对象,然后产生一个holder对象,这个holder对象实际上可以做的事情有很多

    Allows you to control the surface size and format, edit the pixels in the surface, and monitor changes to the surface.

      private void initView() {
            /**
             * 初始化前置相机参数
             */
            // 初始化surface view
            frontSurfaceView = (SurfaceView) findViewById(R.id.front_surfaceview);
            // 初始化surface holder
            frontHolder = frontSurfaceView.getHolder();
        }
    frontCameraManager = new CameraManager(mFrontCamera, frontHolder);

    需要注意的是这时的Camera对象还没有初始化,只有当打开相机时才能初始化,这部分在CameraManager中的打开相机部分有解释。

    3.3 开启摄像头并照相

    先打开摄像头,如果打开成功就试图对焦,并且表示摄像头已经打开,最后进行照相。

      /**
         * @return 开启前置摄像头照相
         */
        private void takeFrontPhoto() {
            if (isFrontOpened == false && frontCameraManager.openCamera(Camera.CameraInfo.CAMERA_FACING_FRONT)) {
                mFrontCamera = frontCameraManager.getCamera();
                //自动对焦  
                mFrontCamera.autoFocus(mAutoFocus);
                isFrontOpened = true;
                // 拍照
                mFrontCamera.takePicture(null, null, frontCameraManager.new PicCallback(mFrontCamera));
            } 
        }
        /**
         * 自动对焦的回调方法,用来处理对焦成功/不成功后的事件
         */
        private AutoFocusCallback mAutoFocus =  new AutoFocusCallback() {
            
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                //TODO:空实现
            }
        }; 

    3.4 本类的全部代码

    package com.kale.camerademo;
    
    import android.app.Activity;
    import android.hardware.Camera;
    import android.hardware.Camera.AutoFocusCallback;
    import android.os.Bundle;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    import android.view.View;
    
    /**
     * @author:Jack Tony
     * @description  :
     * 参考自:http://blog.csdn.net/a740169405/article/details/12207229
     * 
     * 还可以参考:http://mobile.51cto.com/amedia-376703.htm
     * @date  :2015年1月16日
     */
    public class MainActivity extends Activity {
    
        private CameraManager frontCameraManager;
        private CameraManager backCameraManager;
        /**
         * 定义前置有关的参数
         */
        private SurfaceView frontSurfaceView;
        private SurfaceHolder frontHolder;
        private boolean isFrontOpened = false;
        private Camera mFrontCamera;
        /**
         *  定义后置有关的参数
         */
        private SurfaceView backSurfaceView;
        private SurfaceHolder backHolder;
        private boolean isBackOpened = false;
        private Camera mBackCamera;
        
        /**
         * 自动对焦的回调方法,用来处理对焦成功/不成功后的事件
         */
        private AutoFocusCallback mAutoFocus =  new AutoFocusCallback() {
            
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                //TODO:空实现
            }
        }; 
        
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initView();
            
            
        }
        
        
        public void buttonListener(View view) {
            switch (view.getId()) {
            case R.id.openFront_button:
                takeFrontPhoto();
                break;
            case R.id.openBack_button:
                takeBackPhoto();
                break;
    
            default:
                break;
            }
        }
        
        private void initView() {
            /**
             * 初始化前置相机参数
             */
            // 初始化surface view
            frontSurfaceView = (SurfaceView) findViewById(R.id.front_surfaceview);
            // 初始化surface holder
            frontHolder = frontSurfaceView.getHolder();
            frontCameraManager = new CameraManager(mFrontCamera, frontHolder);
            /**
             * 初始化后置相机参数
             */
            // 初始化surface view
            backSurfaceView = (SurfaceView) findViewById(R.id.back_surfaceview);
            // 初始化surface holder
            backHolder = backSurfaceView.getHolder();
            backCameraManager = new CameraManager(mBackCamera, backHolder);
        }
    
        /**
         * @return 开启前置摄像头照相
         */
        private void takeFrontPhoto() {
            if (isFrontOpened == false && frontCameraManager.openCamera(Camera.CameraInfo.CAMERA_FACING_FRONT)) {
                mFrontCamera = frontCameraManager.getCamera();
                //自动对焦  
                mFrontCamera.autoFocus(mAutoFocus);
                isFrontOpened = true;
                // 拍照
                mFrontCamera.takePicture(null, null, frontCameraManager.new PicCallback(mFrontCamera));
            } 
        }
        
        /**
         * @return 开启后置摄像头照相
         */
        private void takeBackPhoto() {
            if (isBackOpened == false && backCameraManager.openCamera(Camera.CameraInfo.CAMERA_FACING_BACK)) {
                mBackCamera = backCameraManager.getCamera();
                //自动对焦  
                mBackCamera.autoFocus(mAutoFocus);
                isBackOpened = true;
                // 拍照
                mBackCamera.takePicture(null, null, backCameraManager.new PicCallback(mBackCamera));
            } 
        }
        
        
    }

    3.5 权限

        <!-- 调用相机权限 -->  
        <uses-permission android:name="android.permission.CAMERA" />  
        <uses-feature android:name="android.hardware.camera" />  
        <uses-feature android:name="android.hardware.camera.autofocus" />  
          
        <!-- 读写SD卡权限 -->  
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  

    四、后台静默照相的思路

    由于保证安全性,android要求camera对象必须提供一个实时的预览框给用户,然用户知道当前相机已经开启。如果我们想要用户在不知情的情况下被照相,那么将预览框做成1dp的大小就行了。

    这里还需要注意,由于打开相机需要时间,照相必须要在打开相机之后进行。如果要进行全自动照相,就必须要等打开相机之后再触发照相的代码。我提供的思路是新开一个线程放开启相机的代码,等待两秒后开始照相。

    protected void takePhoto(){
            //这里得开线程进行拍照,因为Activity还未完全显示的时候,是无法进行拍照的,SurfaceView必须先显示
            new Thread(new Runnable() {
                @Override
                public void run() {
                    takeFrontPhoto();
                }
            }).start();
        }
        
        //开启前置摄像头照相
        private boolean takeFrontPhoto() {
            if(openFacingFrontCamera())
            {
                try {
                    //因为开启摄像头需要时间,这里让线程睡2秒
                    Thread.sleep(2000);
                } catch (InterruptedException e) {}
                //拍照
                myCamera.takePicture(null, null, myPicCallback);
                return true;
            }
            else{
                return false;
            }
        }

    如果你想要同时拍摄前后相机的照片,我目前没有找到很好的方法,理论上是完全可以的。想的办法是在前置拍摄成功后,触发拍摄后置的代码。这个思路挺蠢的,不是很推荐。

    如果你不想要用户打开activity照相,你完全可以用service来做这个事情。做一个悬浮窗,然后给悬浮窗中放预览框,设置悬浮窗大小为1dp。开启服务后自动执行照相的代码。

    源码下载:http://download.csdn.net/detail/shark0017/8370303

    参考自:

    http://bbs.51cto.com/thread-1063856-1.html

  • 相关阅读:
    CentOs 安装 Mysql
    安装 CentOs 系统 及 Python 及 Scrapy 框架
    对IOC的理解
    SQL语句优化 -- 以Mysql为例
    探讨 java中 接口和对象的关系
    Java中组合 设计技巧 实例
    【题解】Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths Codeforces 741D DSU on Tree
    【题解】Tree-String Problem Codeforces 291E AC自动机
    【题解】打地鼠 SDOI2011 模拟 行列无关
    【题解】新型城市化 HAOI2017 网络流 二分图最大匹配 强连通分量
  • 原文地址:https://www.cnblogs.com/tianzhijiexian/p/4230197.html
Copyright © 2011-2022 走看看