Camera2实现基础相机功能

先上效果图:


img111.jpg

文章推荐,没看的可以去看下,将会对Camera的认识更加深刻:
1.Camera开发知识储备
2.了解Matrix
3.Camera1基础开发
4.基于Camera实现人脸检测

Camera2

Camera2相对于Camera1引入了管道的概念:

Camera2的架构图:
Camera2架构图.png
Camera2拍照流程图,同时也是API调用顺序的图:
image.png

流程:
1.创建Camera线程和该线程的handler,绑定并开启线程
2.添加TextureView的监听
3.初始化Sureface,指定cameraId,创建ImageReader并设置监听
4.打开相机
5.创建相机预览会话
6.拍照

多的注解文字我就不写了,直接贴示例代码好了:

/**
 * Camera2
 */
public class Camera2Helper {

    private static final String TAG = "Camera2Helper";
    //默认使用后置摄像头
    private int mFacing = CameraCharacteristics.LENS_FACING_BACK;

    private int PREVIEW_WIDTH = 720;          //预览的宽度
    private int PREVIEW_HEIGHT = 1280;        //预览的高度
    private int PIC_WIDTH = 720;             //保存图片的宽度
    private int PIC_HEIGHT = 1280;           //保存图片的高度
    //相机的信息
    private CameraCharacteristics mCameraCharacteristics;
    //相机ID
    private String cameraID;

    private HandlerThread handlerThread = new HandlerThread("CameraThread");

    private Activity activity;
    private TextureView textureView;
    private Integer mCameraSensorOrientation;
    private StreamConfigurationMap streamConfigurationMap;
    private ImageReader mImageReader;
    private Handler cameraHandler;
    private CameraManager cameraManager;

    private CameraDevice cameraDevice;
    private CameraCaptureSession cameraCaptureSession;
    private boolean canExchangeCamera;
    private boolean canTakePic;

    public Camera2Helper(Activity activity, TextureView textureView) {
        this.activity = activity;
        this.textureView = textureView;
        init();
    }

    public void init() {
        handlerThread.start();
        //将handler绑定到指定线程
        cameraHandler = new Handler(handlerThread.getLooper());
        textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                Log.i(TAG, "    onSurfaceTextureAvailable");
                //初始化相机资源
                initCamera();
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                Log.i(TAG, "    onSurfaceTextureSizeChanged");
            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                Log.i(TAG, "    onSurfaceTextureDestroyed");
                //释放相机资源
                releaseCamera();
                return true;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
                Log.i(TAG, "    onSurfaceTextureUpdated");
                if (cameraDevice == null) {
                    initCamera();
                }
            }
        });
    }

    /**
     * 初始化相机
     */
    public void initCamera() {
        try {
            cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
            if (cameraManager != null) {
                String[] cameraIdList = cameraManager.getCameraIdList();
                if (cameraIdList.length == 0) {
                    Toast.makeText(activity, "暂无相机可用", Toast.LENGTH_SHORT).show();
                    return;
                }
                for (String id : cameraIdList) {
                    CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
                    Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
                    // TODO: 2020/3/19 如何分析后置摄像头的类型
                    if (mFacing == facing) {
                        mCameraCharacteristics = cameraCharacteristics;
                        if (mFacing == CameraCharacteristics.LENS_FACING_BACK && id.equals("2")) {
                            cameraID = id;
                            break;
                        } else
                            cameraID = id;
                    }
                    Log.i(TAG, "摄像头的ID:" + cameraID);
                }

                Integer supportLevel = mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                if (supportLevel != null && supportLevel ==
                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                    Toast.makeText(activity, "相机硬件不支持新特性", Toast.LENGTH_SHORT).show();
                }
                //获取手机的方向
                int screenRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
                //获取摄像头的方向
                mCameraSensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
                //获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
                streamConfigurationMap = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

                //获取支持的图片尺寸
                Size[] pictureSizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
                //获取支持的预览尺寸
                Size[] previewSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);
                //是否需要调换宽高
                boolean exchange = exchangeWidthAndHeight(screenRotation, mCameraSensorOrientation);
                Size size;
                if (exchange) {
                    size = new Size(textureView.getHeight(), textureView.getWidth());
                } else {
                    size = new Size(textureView.getWidth(), textureView.getHeight());
                }
                Log.i(TAG, "    textureView.getWidth() :" + textureView.getWidth()
                        + "   textureView.getHeight():" + textureView.getHeight());
                Size pictureSize = getBestSize(exchange ? PIC_HEIGHT : PIC_WIDTH,
                        exchange ? PIC_WIDTH : PIC_HEIGHT, size, pictureSizes);
                Size previewSize = getBestSize(exchange ? PREVIEW_HEIGHT : PREVIEW_WIDTH,
                        exchange ? PREVIEW_WIDTH : PREVIEW_HEIGHT, size, previewSizes);

                textureView.getSurfaceTexture().setDefaultBufferSize(previewSize.getWidth(),
                        previewSize.getHeight());

                mImageReader = ImageReader.newInstance(pictureSize.getWidth(), pictureSize.getHeight(),
                        ImageFormat.JPEG, 1);
                mImageReader.setOnImageAvailableListener(imageAvailableListener, cameraHandler);

                //打开相机
                openCamera();

            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private ImageReader.OnImageAvailableListener imageAvailableListener =
            new ImageReader.OnImageAvailableListener() {
                @Override
                public void onImageAvailable(ImageReader reader) {
                    //接收图片的数据
                    if (reader != null) {
                        Log.i(TAG, "Image Available!   当前线程:" + Thread.currentThread().getName());
                        Image image = reader.acquireLatestImage();
//                        Image image = reader.acquireNextImage();
//                        new Thread(new ImageSaver(activity, image, mFacing)).start();

                        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String dirPath = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath()
                                + "/Picture/";
                        File dirFile = new File(dirPath);
                        if (!dirFile.exists())
                            dirFile.mkdirs();
                        String picturePath = dirPath + "picture.jpeg";
                        File picFile = new File(picturePath);
                        if (picFile.exists())
                            picFile.delete();

                        transform(data, image, picFile);
                    }
                }
            };

    public void transform(byte[] data, Image image, File filePath) {
        //给图片变换为视觉角度
        if (data != null && data.length > 0) {
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            Matrix matrix = new Matrix();
            if (mFacing == CameraCharacteristics.LENS_FACING_FRONT) {
                //如果时前置摄像头,镜像;因为时镜像,所以旋转角度是360-rotation
                matrix.postScale(-1, 1);
            }
            Bitmap bitmap1 = Bitmap.createBitmap(bitmap, 0, 0,
                    bitmap.getWidth(), bitmap.getHeight(), matrix, true);
            saveBmp(bitmap1, filePath);
            bitmap1.recycle();
            image.close();//这里要关闭image,否则界面会卡
        }
    }

    /**
     * 存储图片
     */
    private void saveBmp(Bitmap bmp, File picFile) {
        try {
            FileOutputStream fos = new FileOutputStream(picFile);
            bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            fos.flush();
            fos.close();
            activity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(activity, "图片保存成功", Toast.LENGTH_SHORT).show();
                }
            });
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            bmp.recycle();
        }
    }

    /**
     * 开启相机
     */
    private void openCamera() {
        if (cameraManager != null) {
            if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
                    == PackageManager.PERMISSION_GRANTED) {
                try {
                    cameraManager.openCamera(cameraID, new CameraDevice.StateCallback() {
                        @Override
                        public void onOpened(@NonNull CameraDevice camera) {
                            Log.i(TAG, "    onOpened()");
                            cameraDevice = camera;
                            //创建一个相机预览的会话
                            createCaptureSession(camera);
                        }

                        @Override
                        public void onDisconnected(@NonNull CameraDevice camera) {
                            Log.i(TAG, "    onDisconnected()");
                            //防止切换相机应用时,当前应用占用了相机导致预览会话创建失败
                            releaseCamera();
                        }

                        @Override
                        public void onError(@NonNull CameraDevice camera, int error) {
                            Log.i(TAG, "    相机打开失败:" + error);
                        }
                    }, cameraHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            } else {
                Toast.makeText(activity, "请开启相机权限", Toast.LENGTH_SHORT).show();
            }

        }
    }

    /**
     * 释放相机资源
     */
    public void releaseCamera() {
        cameraCaptureSession.close();
        cameraCaptureSession = null;

        cameraDevice.close();
        cameraDevice = null;

        mImageReader.close();
        mImageReader = null;

        canExchangeCamera = false;
    }

    /**
     * 退出线程
     */
    public void releaseThread() {
        handlerThread.quitSafely();
    }

    /**
     * 创建一个相机预览的会话
     */
    private void createCaptureSession(CameraDevice cameraDevice) {
        try {
            if (cameraDevice != null) {
                CaptureRequest.Builder captureRequestBuilder =
                        cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                Surface surface = new Surface(textureView.getSurfaceTexture());
                // 将CaptureRequest的构建器与Surface对象绑定在一起
                captureRequestBuilder.addTarget(surface);
                // 禁用闪光灯
                captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                // 添加自动对焦
                captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED);
                CaptureRequest captureRequest = captureRequestBuilder.build();
                //创建会话
                cameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                        new CameraCaptureSession.StateCallback() {
                            @Override
                            public void onConfigured(@NonNull CameraCaptureSession session) {
                                try {
                                    cameraCaptureSession = session;
                                    session.setRepeatingRequest(captureRequest,
                                            captureCallback, cameraHandler);
                                } catch (CameraAccessException e) {
                                    e.printStackTrace();
                                }
                            }

                            @Override
                            public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                            }
                        }, cameraHandler);

            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraCaptureSession.CaptureCallback captureCallback =
            new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureStarted(@NonNull CameraCaptureSession session,
                                             @NonNull CaptureRequest request,
                                             long timestamp, long frameNumber) {
                    super.onCaptureStarted(session, request, timestamp, frameNumber);
                }

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                               @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
//                    unlockFocus();//恢复预览
                    canExchangeCamera = true;
                    canTakePic = true;
                }

                @Override
                public void onCaptureFailed(@NonNull CameraCaptureSession session,
                                            @NonNull CaptureRequest request,
                                            @NonNull CaptureFailure failure) {
                    super.onCaptureFailed(session, request, failure);
                    Toast.makeText(activity, "预览会话创建失败", Toast.LENGTH_SHORT).show();
                }
            };

    /**
     * 拍照
     */
    public void takePhoto() {
        if (cameraDevice == null || !textureView.isAvailable() || !canTakePic) return;
        try {
            CaptureRequest.Builder captureRequest = cameraDevice.
                    createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureRequest.addTarget(mImageReader.getSurface());
            // 自动对焦
            captureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 闪光灯
            captureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            //根据摄像头方向对保存的照片进行旋转,使其为"自然方向"
            captureRequest.set(CaptureRequest.JPEG_ORIENTATION, mCameraSensorOrientation);
            //拍照
            CaptureRequest build = captureRequest.build();
            cameraCaptureSession.capture(build, captureCallback, cameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 切换相机
     */
    public void exchangeCamera() {
        if (cameraDevice == null || !canExchangeCamera || !textureView.isAvailable()) return;
        if (mFacing == CameraCharacteristics.LENS_FACING_FRONT)
            mFacing = CameraCharacteristics.LENS_FACING_BACK;
        else
            mFacing = CameraCharacteristics.LENS_FACING_FRONT;
        releaseCamera();
        initCamera();
    }

    /**
     * 根据手机的方向和相机传感器的方向确定是否需要调换宽高
     *
     * @param screenRotation           手机方向
     * @param mCameraSensorOrientation 相机传感器的方向
     */
    private boolean exchangeWidthAndHeight(int screenRotation, int mCameraSensorOrientation) {
        boolean exchange = false;
        if (screenRotation == Surface.ROTATION_0 || screenRotation == Surface.ROTATION_180) {
            if (mCameraSensorOrientation == 90 || mCameraSensorOrientation == 270) {
                exchange = true;
            }
        } else if (screenRotation == Surface.ROTATION_90 || screenRotation == Surface.ROTATION_270) {
            if (mCameraSensorOrientation == 0 || mCameraSensorOrientation == 180) {
                exchange = true;
            }
        }
        return exchange;
    }

    /**
     * 计算出最佳的尺寸
     *
     * @param targetWidth  targetWidth   图片或预览指定的尺寸
     * @param targetHeight targetHeight   图片或预览指定的尺寸
     * @param size         textureView的尺寸
     * @param supportSizes 支持的尺寸
     * @return
     */
    private Size getBestSize(float targetWidth, float targetHeight, Size size, Size[] supportSizes) {
        //宽高大的Size列表
        ArrayList<Size> bigList = new ArrayList<>();
        //宽高小的Size列表
        ArrayList<Size> smallList = new ArrayList<>();
        for (Size supportSize : supportSizes) {
            //宽<=最大宽度  &&  高<=最大高度  &&  宽高比 == 目标值宽高比
            if ((float) supportSize.getWidth() <= (float) size.getWidth() &&
                    (float) supportSize.getHeight() <= (float) size.getHeight()
                    && targetWidth / targetHeight == (float) supportSize.getWidth()
                    / (float) supportSize.getHeight()) {
                if (supportSize.getWidth() >= targetWidth && supportSize.getHeight() >= targetHeight) {
                    bigList.add(supportSize);
                } else {
                    smallList.add(supportSize);
                }
            }
            Log.i(TAG, "系统支持的尺寸: w " + supportSize.getWidth() + " h "
                    + supportSize.getHeight() + " 比例 :"
                    + supportSize.getWidth() / supportSize.getHeight());
        }
//        Log.i(TAG, "最大尺寸 :w:" + size.getWidth() + "  h: "
//                + size.getHeight() + ", 比例 :" + (size.getWidth() / size.getHeight()));
//        Log.i(TAG, "目标尺寸 :w:" + targetWidth + " h:" +
//                targetHeight + " 比例 :" + (targetWidth / targetHeight));

        //选择bigEnough中最小的值  或 notBigEnough中最大的值
        if (bigList.size() > 0) {
            Size min = Collections.min(bigList, new CompareSizeByArea());
            return min;
        }
        if (smallList.size() > 0) {
            Size max = Collections.max(smallList, new CompareSizeByArea());
            return max;
        } else {
            return supportSizes[0];
        }
    }

    private class CompareSizeByArea implements Comparator<Size> {

        @Override
        public int compare(Size o1, Size o2) {
            return Long.signum((long) o1.getWidth() * o1.getHeight()
                    - (long) o2.getWidth() * o2.getHeight());
        }
    }

}

在Activity中使用:

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextureView textureView = view.findViewById(R.id.textureView);
        ImageView ivSetting = view.findViewById(R.id.ivSetting);
        ImageButton btnTakePic = view.findViewById(R.id.btnTakePic);
        //录制视频的按钮
        ImageView btnStart = view.findViewById(R.id.btnStart);
        ImageView btnStop = view.findViewById(R.id.btnStop);
        //切换相机摄像头的按钮
        ImageView ivExchange = view.findViewById(R.id.ivExchange);

        camera2Helper = new Camera2Helper(getActivity(), textureView);

        ivSetting.setOnClickListener(this);
        btnTakePic.setOnClickListener(this);
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        ivExchange.setOnClickListener(this);
    }

    @Override
    public void onResume() {
        super.onResume();

    }

    @Override
    public void onPause() {
        super.onPause();
        camera2Helper.releaseCamera();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        camera2Helper.releaseThread();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btnTakePic) {
            //拍照
            camera2Helper.takePhoto();
        } else if (v.getId() == R.id.ivExchange) {
            //切换相机
            camera2Helper.exchangeCamera();
        } else if (v.getId() == R.id.ivSetting) {
            //相机设置

        } else if (v.getId() == R.id.btnStart) {
            //开始录制视频

        } else if (v.getId() == R.id.btnStop) {
            //停止录制视频

        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容