Camera1基础开发

如果有人看这篇文章是新手的话可以先看下相机的基础知识,可以看下这篇博客:Android: Camera相机开发详解(上) —— 知识储备

先看看效果:


效果.gif

本篇记录Camera1的机出开发,虽然Camera1在Android中已经被废弃了,但是作为音视频开发的切入点,还是有必要了解下的。文章仅供自己记录用,所以并不会附带很多文字注释:

自定义相机开发的主要流程:

1.创建一个SurfaceView或TextureView的布局用来接收预览数据
2.获取SurfaceView的SurfaceHolder并监听Surface的生命周期
3.在Surface创建后开启相机,设置相机参数,开启预览(设置预览的旋转方向)
4.拍照(旋转或镜像旋转,保存照片),或切换相机
5.释放相机资源

下面贴出Camera工具类,每个方法都有注解:

/**
 * Camera1的工具类
 * 思路:创建SurfaceView或TextureView接收预览数据,
 * 获取SurfaceHolder并监听Surface的生命周期,
 * 在Surface创建后开启相机,设置相机参数,开启预览(设置预览的旋转方向),
 * 拍照(旋转或镜像旋转,保存照片),切换相机,
 * 最后释放相机资源
 */
public class Camera1Helper implements Camera.PreviewCallback, OrientationListener.OnOrientationListener {

    private static final String TAG = "CameraHelper";

    /**
     * 摄像头方向,默认后置摄像头
     */
    private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
    /**
     * 预览旋转的角度
     */
    private int mDisplayOrientation = 0;

    private int useWidth;
    private int useHeight;

    private Activity activity;
    private MySurfaceView surfaceView;
    private final SurfaceHolder surfaceHolder;
    private Camera mCamera;
    private final Point point;
    private int mSensorRotation;
    private boolean isActivedPreview = false;
    //当前方向角度监听
    private OrientationListener orientationListener;

    public Camera1Helper(Activity activity, MySurfaceView surfaceView) {
        this.activity = activity;
        this.surfaceView = surfaceView;
        surfaceHolder = surfaceView.getHolder();
        point = new Point();
        activity.getWindowManager().getDefaultDisplay().getSize(point);
        useHeight = point.y;
        useWidth = point.x;
        Log.i(TAG, "屏幕的尺寸:" + useWidth + "   " + useHeight);
        acceleRometer();
        init();
    }

    /**
     * 开启传感器加速
     */
    public void acceleRometer() {
        orientationListener = new OrientationListener(activity);
        orientationListener.registerListener();
        orientationListener.setOnOrientationListener(this);
    }

    /**
     * 第一步:监听Surface的生命周期
     * 开启相机
     * 销毁相机
     */
    public void init() {
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (mCamera == null) {
                    openCamera();  //打开相机
                }
                startPreview();  //开始预览
            }

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

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                releaseCamera();
            }
        });
    }

    /**
     * 第二步:打开相机
     */
    public void openCamera() {
        mCamera = Camera.open(mCameraFacing);
        initCameraConfig();
        mCamera.setPreviewCallback(this);
    }

    /**
     * 配置相机的信息
     */
    private void initCameraConfig() {
        if (surfaceView != null) {
            surfaceView.setCustomTouchEvent(new MySurfaceView.CustomTouchEvent() {
                @Override
                public void onTouchEvent(MotionEvent event) {
                    handleFocus(event, mCamera);
                }
            });
        }
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setRecordingHint(true);
        parameters.setAutoWhiteBalanceLock(true);
        parameters.setPreviewFormat(ImageFormat.JPEG);
        //获取支持的预览尺寸
        List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
        //获取与指定宽高相近的尺寸
        Camera.Size bestSize = getBestSize(useWidth, useHeight, supportedPreviewSizes);
        parameters.setPreviewSize(bestSize.width, bestSize.height);
        parameters.setPictureSize(bestSize.width, bestSize.height);
        //判断当前相机是否支持当前的对焦模式
        boolean supportedFocusModes = isSupportedFocusModes(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
                parameters.getSupportedFocusModes());
//        if (supportedFocusModes) {
        //设置对焦模式
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
//        }
        //个别机型对摄像头的预览尺寸要求严格,需要指定尺寸
        setParameter(parameters);
    }

    /**
     * 第四步:开启预览
     */
    private void startPreview() {
        try {
            mCamera.setPreviewDisplay(surfaceHolder);
            //设置预览时相机的旋转角度
            setCameraDisplayOrientation();
            mCamera.startPreview();
            isActivedPreview = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 第三步:设置预览角度,setDisplayOrientation本身只能改变预览的角度
     * previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的
     * 拍摄的照片需要自行处理
     * 这里Nexus5X的相机简直没法吐槽,后置摄像头倒置了,切换摄像头之后就出现问题了。
     * 原文链接:https://blog.csdn.net/u010126792/article/details/86529646
     */
    private void setCameraDisplayOrientation() {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraFacing, 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;
        }
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else {
            result = (info.orientation - degrees + 360) % 360;
        }
        mDisplayOrientation = result;
        mCamera.setDisplayOrientation(mDisplayOrientation);
        System.out.println("=========orienttaion=============" + result);
    }

    /**
     * 最后:释放相机资源
     */
    public void releaseCamera() {
        if (mCamera != null) {
            orientationListener.unregisterListener();
            mCamera.stopPreview();
//            surfaceHolder.removeCallback(surfaceHolderCallBack);
            mCamera.setPreviewCallback(null);
            mCamera.lock();
            mCamera.release();
            isActivedPreview = false;
            mCamera = null;
        }
    }

    /**
     * Camera.setParameter
     * 指定相机的预览尺寸和照片尺寸
     */
    private void setParameter(Camera.Parameters parameters) {
        try {
            //个别机型在SupportPreviewSizes里汇报了支持某种预览尺寸,但实际是不支持的,设置进去就会抛出RuntimeException.
            mCamera.setParameters(parameters);
        } catch (Exception e) {
            e.printStackTrace();
            try {
                //遇到上面所说的情况,只能设置一个最小的预览尺寸
                parameters.setPreviewSize(1920, 1080);
                mCamera.setParameters(parameters);
            } catch (Exception ex) {
                ex.printStackTrace();
                try {
                    parameters.setPictureSize(1920, 1080);
                    mCamera.setParameters(parameters);
                } catch (Exception ignored) {
                }
            }
        }
    }

    /**
     * 判断当前的相机是否支持当前的对焦模式
     *
     * @param focusModeContinuousPicture 对焦模式
     * @param supportedFocusModes        支持的对焦模式
     */
    private boolean isSupportedFocusModes(String focusModeContinuousPicture,
                                          List<String> supportedFocusModes) {
        for (String supportedFocusMode : supportedFocusModes) {
            if (supportedFocusMode.equals(focusModeContinuousPicture))
                return true;
        }
        return false;
    }

    /**
     * 获取最佳的预览尺寸
     *
     * @param width                 指定的预览宽度
     * @param height                指定的预览高度
     * @param supportedPreviewSizes 相机支持的预览尺寸
     * @return
     */
    private Camera.Size getBestSize(int width, int height, List<Camera.Size> supportedPreviewSizes) {
        Camera.Size bestSize = null;
        //指定预览的宽高比
        Double rate = (double) height / (double) width;
        Log.i(TAG, "  format:" + rate);
        List<Camera.Size> maxSizeList = new ArrayList<>();
        List<Camera.Size> minSizeList = new ArrayList<>();
        for (Camera.Size size : supportedPreviewSizes) {
            if (width == size.width && height == size.height) {
                return size;
            }
            Double i = (double) size.width / (double) size.height;
            if (i > rate) {
                maxSizeList.add(size);
            } else {
                minSizeList.add(size);
            }
        }
        sortSizeList(maxSizeList);
        sortSizeList(minSizeList);
        Camera.Size size1 = bestSize(point, maxSizeList);
        Camera.Size size2 = bestSize(point, minSizeList);
        if (size1.width > size2.width) {
            bestSize = size1;
        } else {
            bestSize = size2;
        }
        Log.i(TAG, "目标尺寸:width:" + width + " * height" + height);
        Log.i(TAG, "最佳尺寸:width:" + bestSize.width + " * height:" + bestSize.height);
        return bestSize;
    }

    private Camera.Size bestSize(Point point, List<Camera.Size> list) {
        ArrayMap<Integer, Camera.Size> map = new ArrayMap<>();
        List<Integer> list1 = new ArrayList<>();
        for (Camera.Size size1 : list) {
            int abs = Math.abs(point.x - size1.width);
            list1.add(abs);
            map.put(abs, size1);
        }
        sortList(list1);
        return map.get(list1.get(list1.size() - 1));
    }

    private void sortList(List<Integer> list) {
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                if (o1 > o2) {
                    return 1;
                } else if (o1 < o2) {
                    return -1;
                }
                return 0;
            }
        });
    }

    private void sortSizeList(List<Camera.Size> list) {
        Collections.sort(list, new Comparator<Camera.Size>() {
            @Override
            public int compare(Camera.Size o1, Camera.Size o2) {
                if (o1.width > o2.width) {
                    return 1;
                } else if (o1.width < o2.width) {
                    return -1;
                }
                return 0;
            }
        });
    }

    /**
     * 保留两位小数
     *
     * @param o
     * @return
     */
    private Double format(Double o) {
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumFractionDigits(2);
        return Double.valueOf(nf.format(o));
    }

    /**
     * 转换对焦区域
     * 范围(-1000, -1000, 1000, 1000)
     */
    private Rect calculateTapArea(float x, float y, int width, int height, float coefficient) {
        float focusAreaSize = 200;
        int areaSize = (int) (focusAreaSize * coefficient);
        int surfaceWidth = width;
        int surfaceHeight = height;
        int centerX = (int) (x / surfaceHeight * 2000 - 1000);
        int centerY = (int) (y / surfaceWidth * 2000 - 1000);
        int left = clamp(centerX - (areaSize / 2), -1000, 1000);
        int top = clamp(centerY - (areaSize / 2), -1000, 1000);
        int right = clamp(left + areaSize, -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;
    }

    private void handleFocus(MotionEvent event, Camera camera) {
        int viewWidth = useWidth;
        int viewHeight = useHeight;
        //计算对焦区域
        Rect focusRect = calculateTapArea(event.getX(), event.getY(),
                viewWidth, viewHeight, 1.0f);
        //一定要首先取消
        camera.cancelAutoFocus();
        Camera.Parameters params = camera.getParameters();
        if (params.getMaxNumFocusAreas() > 0) {
            List<Camera.Area> focusAreas = new ArrayList<>();
            focusAreas.add(new Camera.Area(focusRect, 800));
            params.setFocusAreas(focusAreas);
        } else {
            //focus areas not supported
        }
        //首先保存原来的对焦模式,然后设置为macro,对焦回调后设置为保存的对焦模式
        final String currentFocusMode = params.getFocusMode();
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
        setParameter(params);
        if (isActivedPreview) {
            camera.autoFocus(new Camera.AutoFocusCallback() {
                @Override
                public void onAutoFocus(boolean success, Camera camera) {
                    //回调后 还原模式
                    Camera.Parameters params = camera.getParameters();
                    params.setFocusMode(currentFocusMode);
                    camera.setParameters(params);
                    if (success) {
                        Toast.makeText(activity, "对焦区域对焦成功", Toast.LENGTH_SHORT).show();
                    }
                }
            });
        }
    }

    /**
     * 切换摄像头
     */
    public void switchCamera() {
        releaseCamera();
        if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
            mCameraFacing = Camera.CameraInfo.CAMERA_FACING_FRONT;
        } else {
            mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
        }
        openCamera();
        startPreview();
    }

    /**
     * 拍摄照片
     */
    public void takePhoto() {
        mCamera.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                Log.i(TAG, "线程:" + Thread.currentThread().getName());
                camera.startPreview();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        String state = Environment.getExternalStorageState();
                        if (state.equals(Environment.MEDIA_MOUNTED)) {
                            String dirPath = activity.getExternalFilesDir(
                                    Environment.DIRECTORY_PICTURES).getPath() + "/focus/";
                            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();
                            //给图片变换为视觉角度
                            if (data != null && data.length > 0) {
                                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                                Matrix matrix = new Matrix();
                                //利用传感器获取当前屏幕的角度加上预览的角度
                                int rotation = /*(*/mDisplayOrientation /*+ mSensorRotation) % 360*/;
                                if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                                    //如果时后置摄像头,则直接旋转为特定的角度
                                    matrix.setRotate(rotation);
                                } else if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                                    //如果时前置摄像头,镜像;因为时镜像,所以旋转角度是360-rotation
                                    rotation = (360 - rotation) % 360;
                                    matrix.setRotate(rotation);
                                    matrix.postScale(-1, 1);
                                }
                                Bitmap bitmap1 = Bitmap.createBitmap(bitmap, 0, 0,
                                        bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                                //存储图片
                                saveBmp(bitmap1, picFile);
                            } else {
                                Log.i(TAG, "图片数据无效");
                            }
                        }
                    }
                }).start();
            }
        });
    }

    /**
     * 存储图片
     */
    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();
        }
    }

    /**
     * 计算传感器的方向
     * https://blog.csdn.net/u010126792/article/details/86706199
     */
    private int calculateSensorRotation(float x, float y) {
        //x是values[0]的值,X轴方向加速度,从左侧向右侧移动,values[0]为负值;从右向左移动,values[0]为正值
        //y是values[1]的值,Y轴方向加速度,从上到下移动,values[1]为负值;从下往上移动,values[1]为正值
        //不考虑Z轴上的数据,
        if (Math.abs(x) > 6 && Math.abs(y) < 4) {
            if (x > 6) {
                return 270;
            } else {
                return 90;
            }
        } else if (Math.abs(y) > 6 && Math.abs(x) < 4) {
            if (y > 6) {
                return 0;
            } else {
                return 180;
            }
        }
        return -1;
    }

    /**
     * 在此处接收相机的预览数据
     *
     * @param data   相机的预览数据
     * @param camera 相机
     */
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {

    }

    @Override
    public void onOrientationChanged(float azimuth, float pitch, float roll) {
        Log.d(TAG, "航向角:" + azimuth + "--俯仰角:" + pitch + "--翻滚角:" + roll);
        mSensorRotation = (int) azimuth;
    }
}

Activity中的调用

 @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        surfaceView = view.findViewById(R.id.surfaceView);
        switchCamera = view.findViewById(R.id.switchCamera);
        takePhoto = view.findViewById(R.id.takePhoto);

        if (camera1Helper == null)
            camera1Helper = new Camera1Helper(Objects.requireNonNull(getActivity()), surfaceView);

        switchCamera.setOnClickListener(this);
        takePhoto.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.switchCamera) {
            camera1Helper.switchCamera();
        } else if (v.getId() == R.id.takePhoto) {
            camera1Helper.takePhoto();
        }
    }
在设置Camera的配置的时候,图像数据类型支持多种类型,设置成parameters.setPreviewFormat(ImageFormat.JPEG);预览时输出的图像就是JPEG的数据
预览的图像数据来源于硬件图像传感器,图像传感器固定在手机上,所以方向是固定的,一般手机的后置摄像头是横向固定相对于手持手机竖直方向旋转90度。前置摄像头刚好相反,获取的数据需要镜像处理

推荐文章:Android Camera预览角度和拍照保存图片角度学习
Android Orientation Sensor监听方向

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

推荐阅读更多精彩内容