[Android 相机]Android 相机开发的基本流程


版权声明:本文使用https://creativecommons.org/licenses/by-nc-nd/4.0/规定的《署名-非商业性使用-禁止演绎 4.0 国际》协议 https://blog.csdn.net/bluewindtalker/article/details/54563910

相机开发现在有2个类,分别为android.hardware.camera2和android.hardware.Camera,其中Camera类官方已经不推荐,不过鉴于有前人踩坑了,为了快速开发也就直接拿来用了

This class was deprecated in API level 21.
We recommend using the new android.hardware.camera2 API for new applications.

关于旧版的Camera类,google官方给了下面的指导步骤,https://developer.android.com/reference/android/hardware/Camera.html

To take pictures with this class, use the following steps:


看了这些后我们可以简单的进行实战,首先是初始化camera的过程。

    /**
     * 初始化照片
     */
    private void initCamera() {
        if (camera != null) {
            camera.startPreview();
        }
        Log.e(TAG, "initCamera");
        //1. Obtain an instance of Camera from open(int).
        //这里可以根据前后摄像头设置
        camera = openCamera(currentCameraType);
        if (camera == null) {
            return;
        }
        //2. Get existing (default) settings with getParameters().
        //获得存在的默认配置属性
        Camera.Parameters parameters = camera.getParameters();

        //3. If necessary, modify the returned Camera.Parameters object and call setParameters(Camera.Parameters).
        //可以根据需要修改属性,这些属性包括是否自动持续对焦、拍摄的gps信息、图片视频格式及大小、预览的fps、
        // 白平衡和自动曝光补偿、自动对焦区域、闪光灯状态等。
        //具体可以参阅https://developer.android.com/reference/android/hardware/Camera.Parameters.html
        if (parameters.getSupportedFocusModes().contains(Camera.Parameters
                .FOCUS_MODE_CONTINUOUS_PICTURE)) {
            //自动持续对焦
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }
        //在设置图片和预览的大小时要注意当前摄像头支持的大小,不同手机支持的大小不同,如果你的SurfaceView不是全屏,有可能被拉伸。
        // parameters.getSupportedPreviewSizes(),parameters.getSupportedPictureSizes()
        List<Camera.Size> picSizes = parameters.getSupportedPictureSizes();
        Resources resources = this.getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        float density = dm.density;
        int width = dm.widthPixels;
        int height = dm.heightPixels;
        Camera.Size picSize = getPictureSize(picSizes, width, height);
        parameters.setPictureSize(picSize.width, picSize.height);
        camera.setParameters(parameters);
        //4. Call setDisplayOrientation(int) to ensure correct orientation of preview.
        //你可能会遇到画面方向和手机的方向不一致的问题,竖向手机的时候,但是画面是横的,这是由于摄像头默认捕获的画面横向的
        // 通过调用setDisplayOrientation来设置PreviewDisplay的方向,可以解决这个问题。
        setCameraDisplayOrientation(this, currentCameraType, camera);

        //5. Important: Pass a fully initialized SurfaceHolder to setPreviewDisplay(SurfaceHolder).
        // Without a surface, the camera will be unable to start the preview.
        //camera必须绑定一个surfaceview才可以正常显示。
        try {
            camera.setPreviewDisplay(displaySfv.getHolder());
        } catch (IOException e) {
            e.printStackTrace();
        }
        //6. Important: Call startPreview() to start updating the preview surface.
        // Preview must be started before you can take a picture.
        //在调用拍照之前必须调用startPreview()方法,但是在此时有可能surface还未创建成功。
        // 所以加上SurfaceHolder.Callback(),在回调再次初始化下。
        camera.startPreview();
        //7. When you want, call
        // takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)
        // to capture a photo. Wait for the callbacks to provide the actual image data.
        //当如果想要拍照的时候,调用takePicture方法,这个下面我们会讲到。

        //8. After taking a picture, preview display will have stopped. To take more photos, call startPreview() again first.
        //在拍照结束后相机预览将会关闭,如果要再次拍照需要再次调用startPreview()

        //9. Call stopPreview() to stop updating the preview surface.
        //通过调用stopPreview方法可以结束预览
        //10. Important: Call release() to release the camera for use by other applications.
        // Applications should release the camera immediately in onPause()(and re-open() it in onResume()).
        //建议在onResume调用open的方法,在onPause的时候执行release方法
    }
根据上文提到的第9、10步骤我们在onResume与onPause做处理。
@Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG, "onResume");
        if (!isRequestPermission) {
            checkAndInitCamera();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(TAG, "onPause");
        releaseCamera();

    }

    private void releaseCamera() {
        if (camera != null) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

其中checkAndInitCamera()为权限处理的方法

private void checkAndInitCamera() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 检查该权限是否已经获取
            int i = ContextCompat.checkSelfPermission(this, permissions[0]);
            // 权限是否已经 授权 GRANTED---授权  DINIED---拒绝
            if (i != PackageManager.PERMISSION_GRANTED) {
                // 如果没有授予该权限,就去提示用户请求
                isRequestPermission = true;
                ActivityCompat.requestPermissions(this, permissions, CAMERA_PERMISSION_CODE);
            } else {
                initCamera();
            }
        } else {
            initCamera();
        }
    }
这里有些细节需要注意,在 parameters.setPictureSize(int  width , int  height ); 这个方法的时候
不能将宽高随意写,必须从 parameters.getSupportedPictureSizes(); 中选择最合适的宽高,否则会出现setParameters failed的运行时错误。

而系统提供的宽高是根据摄像头的参数定的,这个导致需要根据手机和surfaceview宽高来动态适配,否则可能会出现图像失真拉伸压缩的情况,在本文中将直接使用最接近的摄像头像素的算法

/**
     * 获得最合是的宽高size
     */
    private Camera.Size getPictureSize(List<Camera.Size> picSizes, int width, int height) {
        Camera.Size betterSize = null;
        int diff = Integer.MAX_VALUE;
        if (picSizes != null && picSizes.size() > 0) {
            for (Camera.Size size : picSizes) {
                int newDiff = Math.abs(size.width - width) + Math.abs(size.height - height);
                if(newDiff == 0){
                    return size;
                }
                if (newDiff < diff) {
                    betterSize = size;
                    diff = newDiff;
                }
            }
        }
        return betterSize;
    }
还有一个细节是摄像头并不是正的,调用的方法
setCameraDisplayOrientation
//设置相机的方向
    public int setCameraDisplayOrientation(Activity activity, int cameraId, android.hardware.Camera camera) {
        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
            default:
                degrees = 0;
                break;
        }
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;   // compensate the mirror
        } else {
            // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);
        return degrees;
    }
https://blog.csdn.net/bluewindtalker/article/details/54563910

这么做运行的时候,我们发现预览图并不能正常显示出来,这是因为surface还没有正常创建出来,这时候我们可以在initCamera方法中加入如下代码,坚挺SurfaceHolder的事件回调

SurfaceHolder holder = displaySfv.getHolder();
        if (holder != null) {
            holder.addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                    Log.e(TAG, "surfaceCreated" + holder);
                    checkAndInitCamera();
                }

                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                    Log.e(TAG, "surfaceChanged" + holder);
                }

                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                    Log.e(TAG, "surfaceDestroyed" + holder);
                }
            });
        }

以下就是重点了,拍照,拍照就是触发一个回调事件方法。

    /**
     * 拍摄照片
     */
    private void takePicture() {
        picIV.setImageBitmap(null);
        if (camera == null) {
            return;
        }
        //如果不加第一个回调,手机会没有拍照音效,第二个回调是返回raw格式图片,
        // 了解过相机的人可能知道这是原图的意思,这个我们不处理,我们处理第三个回调,jpg格式的数据
        // 拍摄照片
        camera.takePicture(new Camera.ShutterCallback() {
            @Override
            public void onShutter() {
            }
        }, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                // 将拍照数据data数组转化为Bitmap,这里应该放到线程执行了,这里为了简单处理直接放UI线程了
                Bitmap imageBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                //一般手机需要旋转90度来适应方向,如果setCameraDisplayOrientation得到的结果不是90度,一般还需要再次旋转180
                picIV.setImageBitmap(rotate(imageBitmap, 90));
                picFl.setVisibility(View.VISIBLE);
            }
        });
    }

    public Bitmap rotate(Bitmap bitmap, int degree) {
        Matrix matrix = new Matrix();
        matrix.postRotate(degree);
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
    }

 以下是布局文件内容

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.bluewindtalker.camera.demo.CameraActivity">

    <SurfaceView
        android:id="@+id/sf_display_demo_camera"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/btn_take_picture_demo_camera"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="70dp"
        android:background="@android:color/black"
        android:gravity="center"
        android:text="拍照"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <FrameLayout
        android:id="@+id/fl_picture_demo_camera"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/black"
        android:visibility="gone">

        <ImageView
            android:id="@+id/iv_picture_demo_camera"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitCenter" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:background="#66666666"
            android:padding="20dp"
            android:text="点击任意区域关闭照片"
            android:textColor="@android:color/white"
            android:textSize="18sp" />
    </FrameLayout>
</FrameLayout>

下篇将讲解如何通过摄像头识别周围环境光亮强度

[Android相机]光线传感器识别环境光亮强度

[Android相机]通过手机摄像头识别环境亮度


  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值