Android自定义相机实践记录

Android自定义相机实践记录

前段时间的工作有这样一个需求,就是打开Android系统的摄像头,在预览模式Priview模式下获取原生的帧数据,这篇文章先不管这些。做完那个需求后,不过瘾,我就继续完善相机,实现了一个自定义的相机。写篇文章记录一下,总结一下相机应用的开发。

1、前置、后置摄像头的判断

一般手机都会有前置和后置摄像头,但是这里还是介绍一下判断的方法。

/**
* 判断是否有前置摄像头
*/
private boolean hasFrontCamera(){
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        //获取手机上摄像头的数量
        int cameraCount = Camera.getNumberOfCameras();
        for (int i = 0 ; i < cameraCount ; i++){
            Camera.getCameraInfo(i,cameraInfo);
            //循环判断
            //同样,如果是判断后置摄像头,就需要Camera.CameraInfo.CAMERA_FACING_BACK
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
                return true;
            }
        }
        return true;
    }

当前,判断前置摄像头还可以通过下面的方式判断

//是否有前置摄像头
isHasFrontCamera = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
//是否支持自动聚焦
isSupportAutoFocus = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS);
//是否支持闪光灯
isSupportFlash = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
//等等,还有其他的功能判断

2、SurfaceView的使用

开发相机,必然是对界面UI的刷新频率非常高,这就需要使用SurfaceView。关于这一段,网上到处都是,直接上代码:

public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback,View.OnClickListener,View.OnTouchListener{

    private static final String TAG = "CameraActivity";
    private SurfaceHolder mHolder;
    private SurfaceView mSurfaceView;
    private Button mBtnSearch;
    private Button mBtnTakePhoto;
    private View focusIndex;
    private ImageView flashBtn;
    private int mCurrentCameraId = 0; // 1是前置 0是后置
    private Camera mCamera;
    private CameraDoHelper mCameraHelper;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        initView();
        initData();
    }

    private void initView() {
        focusIndex = findViewById(R.id.focus_index);
        flashBtn = (ImageView) findViewById(R.id.flash_view);
        mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        //mCameraGrid = (CameraGrid) findViewById(R.id.camera_grid);
        mBtnSearch = (Button) findViewById(R.id.search);
        mBtnTakePhoto = (Button) findViewById(R.id.takephoto);
        mSurfaceView.setOnTouchListener(this);
    }

    private void initData() {
        //通过SurfaceView对象获取SurfaceHolder对象,我们主要使用它来处理相机应用
        mHolder = mSurfaceView.getHolder();
        //在这里需要注册回调接口,需要实现surfaceCreated,surfaceChanged,surfaceDestroyed三个方法。
        mHolder.addCallback(this);
        //保持屏幕常亮
        mHolder.setKeepScreenOn(true);
    }

    @Override
    protected void onResume() {
        super.onResume();
//        int numCams = Camera.getNumberOfCameras();
//        if (numCams > 0) {
//            try {
//                mCurrentCameraId = 0;
//                mCamera = Camera.open(mCurrentCameraId);
//                mCamera.startPreview();
//            } catch (RuntimeException ex) {
//                Toast.makeText(getApplicationContext(), "未发现相机", Toast.LENGTH_LONG).show();
//            }
//        }
    }

/*创建,或者重新创建
1,刚进入当前Activity时调用。
2、打开另一个Activity时会调用surfaceDestroyed
3、从另一个Activity再回来时,就会调用surfaceCreated
*/
    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        Log.i(TAG,"surfaceCreated");
        int numCams = Camera.getNumberOfCameras();
        if (numCams > 0) {
            try {
                //后置摄像头为0,前置摄像头为1
                mCurrentCameraId = 0;
                //打开相机,并获取打开的相机的对象
                mCamera = Camera.open(mCurrentCameraId);
            } catch (RuntimeException ex) {
                Toast.makeText(getApplicationContext(), "未发现相机", Toast.LENGTH_LONG).show();
            }
        }
        if (mCamera != null){
        //进行一系列的初始化,后面说
            CameraDoHelper.getInstance(this).initCamera(mCamera,mCurrentCameraId);
        }

    }

    //这个方法至少会在surfaceCreated之后调用。在尺寸发生变化时也会调用,比如横竖屏幕的转化
    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int w, int h) {
        Log.i(TAG,"surfaceChanged");
        if (surfaceHolder.getSurface() == null) {
            return;
        }
        if (mCamera != null) {
            try {
            //这里就是让Camera对象和SurfaceView关联起来,让相机预览的界面放在SurfaceView上
                mCamera.setPreviewDisplay(surfaceHolder);
                //开始预览
                mCamera.startPreview();
                //自动对焦一次                                                                                                                                                                                                                                                                                                                     
                CameraDoHelper.getInstance(this).reAutoFocus(mCamera);
            } catch (IOException e) {
                e.printStackTrace();
                if (mCamera != null){
                    mCamera.release();
                    mCamera = null;
                }
            }

        }
    }

    //另一个Activity覆盖当前的Activity时,会调用这个方法。再回来时,就会调用surfaceCreated方法
    //点击退出当前Activity时,先于Ondestory
    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        Log.i(TAG,"surfaceDestroyed");
        if (mCamera != null){
        //如果不加这句,退出Activity时,会出现异常
            mCamera.setPreviewCallback(null);
            //停止预览
            mCamera.stopPreview();
            //释放资源
            mCamera.release();
            mCamera = null;
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
        /*case R.id.camera_back:
            setResult(0);
            finish();
            break;*/

            case R.id.camera_flip_view:
                //转化前置或者后置摄像头
                switchCamera();
                break;

            case R.id.flash_view:
                turnLight();
                break;

            case R.id.action_button:
                CameraDoHelper.getInstance(this).takePhoto(mCamera);
                break;

            case R.id.search:   //处理选中状态
                mBtnSearch.setSelected(true);
                mBtnTakePhoto.setSelected(false);
                break;

            case R.id.takephoto:    //处理选中状态
                mBtnTakePhoto.setSelected(true);
                mBtnSearch.setSelected(false);
                break;
        }
    }

    // 切换前后置摄像头
    public void switchCamera() {
        mCurrentCameraId = (mCurrentCameraId + 1) % Camera.getNumberOfCameras();
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
        }
        //切换摄像头需要将前一个摄像头完全停止
        try {
            mCamera = Camera.open(mCurrentCameraId);
            mCamera.setPreviewDisplay(mHolder);
            CameraDoHelper.getInstance(this).initCamera(mCamera,mCurrentCameraId);
            mCamera.startPreview();
        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), "未发现相机", Toast.LENGTH_LONG).show();
        }

    }


3、设置相机属性

我们可以设置相机的很多属性,比如相机预览界面的大小、照片格式与大小、闪光灯等。

public void initCamera(Camera camera,int cameraId){
        if (camera != null) {
            mCurrentCameraId = cameraId;
            mCamera = camera;
            //在这里获取当前相机的参数属性的对象
            Camera.Parameters params = camera.getParameters();
            //这里是一个获取最佳的预览size的方法
            mPreviewSize = findBestPreviewResolution(camera);
            //这里是一个获取最佳的照片size的方法
            adapterSize = findBestPictureResolution(camera);
            if (adapterSize != null) {
            //设置拍照照片的size
                params.setPictureSize(adapterSize.width, adapterSize.height);
            }
            if (mPreviewSize != null) {
            //设置预览的size
                params.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
            }
            //设置照片格式,这里是JPEG,还有
            params.setPictureFormat(PixelFormat.JPEG);
            //获取当前相机支持的聚焦模式,一般后置摄像头支持聚焦,前置摄像头不支持聚焦。
            //聚焦的模式有自动聚焦、连续自动对焦等
            List<String> focusModes = params.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                // set the focus mode
                params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            }
            setDispaly();
            //setCameraDisplayOrientation((Activity) mContext, mCurrentCameraId, mCamera);
            camera.setParameters(params);
        }
    }
/**
  * 闪光灯开关 开->关->自动
  */
private void turnLight() {
//mCamera.getParameters().getSupportedFlashModes()获取支持的闪光模式
        if (mCamera == null || mCamera.getParameters() == null
                || mCamera.getParameters().getSupportedFlashModes() == null) {
            return;
        }
        Camera.Parameters parameters = mCamera.getParameters();
        String flashMode = mCamera.getParameters().getFlashMode();
        List<String> supportedModes = mCamera.getParameters()
                .getSupportedFlashModes();
                //当前是关闭闪光灯
        if (Camera.Parameters.FLASH_MODE_OFF.equals(flashMode)
                && supportedModes.contains(Camera.Parameters.FLASH_MODE_ON)) {
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
            mCamera.setParameters(parameters);
            flashBtn.setImageResource(R.mipmap.camera_flash_on);
            //当前是开启状态
        } else if (Camera.Parameters.FLASH_MODE_ON.equals(flashMode)) {
            if (supportedModes.contains(Camera.Parameters.FLASH_MODE_AUTO)) {
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
                flashBtn.setImageResource(R.mipmap.camera_flash_auto);
                mCamera.setParameters(parameters);
            } else if (supportedModes
                    .contains(Camera.Parameters.FLASH_MODE_OFF)) {
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                flashBtn.setImageResource(R.mipmap.camera_flash_off);
                mCamera.setParameters(parameters);
            }
        } else if (Camera.Parameters.FLASH_MODE_AUTO.equals(flashMode)
                && supportedModes.contains(Camera.Parameters.FLASH_MODE_OFF)) {
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
            mCamera.setParameters(parameters);
            flashBtn.setImageResource(R.mipmap.camera_flash_off);
        }
    }

4、拍照

public void takePhoto(Camera camera) {
        try {
        //这里有三个回调接口对象
            camera.takePicture(shutterCallback, rawCallback, jpegCallback);
        } catch (Throwable t) {
            t.printStackTrace();
            Toast.makeText(mContext, "拍照失败,请重试!", Toast.LENGTH_LONG)
                    .show();
            try {
                camera.startPreview();
            } catch (Throwable e) {

            }
        }
    }

//这个就是点击相机的快门调用,一般默认会有咔嚓的声音
    Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() {
        public void onShutter() {
        //可以发声音
        }
    };
    //获取原始数据
    Camera.PictureCallback rawCallback = new Camera.PictureCallback() {
        public void onPictureTaken(byte[] data, Camera camera) {
        }
    };

//拍照完成时回调
    Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
        public void onPictureTaken(byte[] data, Camera camera) {
        //保存图片
            new SaveImageTask(data,mContext,mCurrentCameraId).execute();
            resetCamera(camera);
        }
    };

5、点击区域聚焦的实现

//当前的Activity中,实现这个方法
//onTouch主要是实现点击进行区域聚焦的功能
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            //将用户手指点击的点的坐标传过去
                CameraDoHelper.getInstance(this).pointFocus(motionEvent);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //这里是为了点击屏幕出现一个聚焦的框
        RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(focusIndex.getLayoutParams());
        layout.setMargins((int) motionEvent.getX() - 60, (int) motionEvent.getY() - 60, 0,0);
        //focusIndex就是一个聚焦的框
        focusIndex.setLayoutParams(layout);
        focusIndex.setVisibility(View.VISIBLE);
        //给聚焦的框一个大小的动画
        ScaleAnimation sa = new ScaleAnimation(3f, 1f, 3f, 1f,
                ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
                ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
        sa.setDuration(800);
        focusIndex.startAnimation(sa);
        handler.postAtTime(new Runnable() {
            @Override
            public void run() {
                focusIndex.setVisibility(View.INVISIBLE);
            }
        }, 800);
        return false;
    }
}

//定点对焦的代码
    public void pointFocus(MotionEvent event) {
        mCamera.cancelAutoFocus();
        Camera.Parameters parameters = mCamera.getParameters();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            //showPoint(x, y);
            focusOnTouch(event);
        }
        mCamera.setParameters(parameters);
        autoFocus(mCamera);
    }

    //实现自动对焦
    public void autoFocus(final Camera camera) {
        new Thread() {
            @Override
            public void run() {
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (camera == null) {
                    return;
                }
                camera.autoFocus(new Camera.AutoFocusCallback() {
                    @Override
                    public void onAutoFocus(boolean success, Camera camera) {
                        if (success) {
                            initCamera(camera,mCurrentCameraId);//实现相机的参数初始化
                        }
                    }
                });
            }
        };
    }
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void focusOnTouch(MotionEvent event) {
    //这里是计算需要聚焦的区域
        Rect focusRect = calculateTapArea(event.getRawX(), event.getRawY(), 1f);
        Rect meteringRect = calculateTapArea(event.getRawX(), event.getRawY(), 1.5f);

        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);

        if (parameters.getMaxNumFocusAreas() > 0) {
            List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
            focusAreas.add(new Camera.Area(focusRect, 1000));

            parameters.setFocusAreas(focusAreas);
        }

        if (parameters.getMaxNumMeteringAreas() > 0) {
            List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
            meteringAreas.add(new Camera.Area(meteringRect, 1000));

            parameters.setMeteringAreas(meteringAreas);
        }
        mCamera.setParameters(parameters);
        mCamera.autoFocus(this);
    }


    @Override
    public void onAutoFocus(boolean success, Camera camera) {
        Log.d(TAG, "onAutoFocus : " + success);
    }

    /**
     * Convert touch position x:y to {@link Camera.Area} position -1000:-1000 to 1000:1000.
     */
    private Rect calculateTapArea(float x, float y, float coefficient) {
        float focusAreaSize = 300;
        int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();

        int centerX = (int) (x / getResolution().width * 2000 - 1000);
        int centerY = (int) (y / getResolution().height * 2000 - 1000);

        int left = clamp(centerX - areaSize / 2, -1000, 1000);
        int right = clamp(left + areaSize, -1000, 1000);
        int top = clamp(centerY - areaSize / 2, -1000, 1000);
        int bottom = clamp(top + areaSize, -1000, 1000);

        return new Rect(left, top, right, bottom);
    }

    private int clamp(int x, int min, int max) {
        if (x > max) {
            return max;
        }
        if (x < min) {
            return min;
        }
        return x;
    }

    public Camera.Size getResolution() {
        Camera.Parameters params = mCamera.getParameters();
        Camera.Size s = params.getPreviewSize();
        return s;
    }

6、保存图片

保存图片其实也有很多的需要考虑的地方,比如,如果我们的相机应用为竖屏应用,这个时候我们预览图像会偏转90度,而且我们排除的图像也会偏转,用户看到的图片会偏转90度的倍数的角度。所以,这个地方需要做一些处理。

public class SaveImageTask extends AsyncTask<Void, Void, String> {
    private byte[] data;
    private Context mContext;
    private GenericProgressDialog mAlertDialog;
    private int mCurrentCameraId;

    SaveImageTask(byte[] data,Context context,int currentCameraId) {
        this.data = data;
        mContext = context;
        mCurrentCameraId = currentCameraId;
    }

    @Override
    protected String doInBackground(Void... params) {
        // Write to SD Card
        String path = "";
        try {
            showProgressDialog("处理中");
            path = FileUtil.saveToSDCard(data,mCurrentCameraId);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
        return path;
    }


    @Override
    protected void onPostExecute(String path) {
        super.onPostExecute(path);
        if (!TextUtils.isEmpty(path)) {
            Log.d("DemoLog", "path=" + path);
            dismissProgressDialog();
        } else {
            Toast.makeText(mContext, "拍照失败,请稍后重试!",
                    Toast.LENGTH_LONG).show();
        }
    }
public class FileUtil {

    private static int PHOTO_SIZE_W = 2000;
    private static int PHOTO_SIZE_H = 2000;

    /**
     * 将拍下来的照片存放在SD卡中
     */
    public static String saveToSDCard(byte[] data,int currentCameraId) throws IOException {
        Bitmap croppedImage;
        // 获得图片大小
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(data, 0, data.length, options);
        // PHOTO_SIZE = options.outHeight > options.outWidth ? options.outWidth
        // : options.outHeight;
        PHOTO_SIZE_W = options.outWidth;
        PHOTO_SIZE_H = options.outHeight;
        options.inJustDecodeBounds = false;
        Rect r = new Rect(0, 0, PHOTO_SIZE_W, PHOTO_SIZE_H);
        try {
            //对图片进行一下修剪
            croppedImage = decodeRegionCrop(data, r,currentCameraId);
        } catch (Exception e) {
            return null;
        }
        String imagePath = "";
        try {
            imagePath = saveToFile(croppedImage);
        } catch (Exception e) {

        }
        croppedImage.recycle();
        return imagePath;
    }



    private static Bitmap decodeRegionCrop(byte[] data, Rect rect,int currentCameraId) {
        InputStream is = null;
        System.gc();
        Bitmap croppedImage = null;
        try {
            is = new ByteArrayInputStream(data);
            //BitmapRegionDecoder显示大图片的某一矩形区域
            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is,false);
            try {
                //显示指定的区域
                croppedImage = decoder.decodeRegion(rect,
                        new BitmapFactory.Options());
            } catch (IllegalArgumentException e) {
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {

        }
        //进行旋转
        Matrix m = new Matrix();
        m.setRotate(90, PHOTO_SIZE_W / 2, PHOTO_SIZE_H / 2);
        if (currentCameraId == 1) {
            //反转图像
            m.postScale(1, -1);
        }
        Bitmap rotatedImage = Bitmap.createBitmap(croppedImage, 0, 0,
                PHOTO_SIZE_W, PHOTO_SIZE_H, m, true);
        if (rotatedImage != croppedImage)
            croppedImage.recycle();
        return rotatedImage;
    }



    // 保存图片文件
    public static String saveToFile(Bitmap croppedImage)
            throws FileNotFoundException, IOException {
        File sdCard = Environment.getExternalStorageDirectory();
        File dir = new File(sdCard.getAbsolutePath() + "/DCIM/Camera/");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        String fileName = getCameraPath();
        File outFile = new File(dir, fileName);
        FileOutputStream outputStream = new FileOutputStream(outFile); // 文件输出流
        croppedImage.compress(Bitmap.CompressFormat.JPEG, 70, outputStream);
        outputStream.flush();
        outputStream.close();
        return outFile.getAbsolutePath();
    }

    private static String getCameraPath() {
        Calendar calendar = Calendar.getInstance();
        StringBuilder sb = new StringBuilder();
        sb.append("IMG");
        sb.append(calendar.get(Calendar.YEAR));
        int month = calendar.get(Calendar.MONTH) + 1; // 0~11
        sb.append(month < 10 ? "0" + month : month);
        int day = calendar.get(Calendar.DATE);
        sb.append(day < 10 ? "0" + day : day);
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        sb.append(hour < 10 ? "0" + hour : hour);
        int minute = calendar.get(Calendar.MINUTE);
        sb.append(minute < 10 ? "0" + minute : minute);
        int second = calendar.get(Calendar.SECOND);
        sb.append(second < 10 ? "0" + second : second);
        if (!new File(sb.toString() + ".jpg").exists()) {
            return sb.toString() + ".jpg";
        }

        StringBuilder tmpSb = new StringBuilder(sb);
        int indexStart = sb.length();
        for (int i = 1; i < Integer.MAX_VALUE; i++) {
            tmpSb.append('(');
            tmpSb.append(i);
            tmpSb.append(')');
            tmpSb.append(".jpg");
            if (!new File(tmpSb.toString()).exists()) {
                break;
            }

            tmpSb.delete(indexStart, tmpSb.length());
        }

        return tmpSb.toString();
    }
}

以上就是全部的步骤,下面补全代码:
请看下一篇。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值