Camera1基礎(chǔ)開發(fā)

如果有人看這篇文章是新手的話可以先看下相機的基礎(chǔ)知識,可以看下這篇博客:Android: Camera相機開發(fā)詳解(上) —— 知識儲備

先看看效果:


效果.gif

本篇記錄Camera1的機出開發(fā),雖然Camera1在Android中已經(jīng)被廢棄了,但是作為音視頻開發(fā)的切入點,還是有必要了解下的。文章僅供自己記錄用,所以并不會附帶很多文字注釋:

自定義相機開發(fā)的主要流程:

1.創(chuàng)建一個SurfaceView或TextureView的布局用來接收預(yù)覽數(shù)據(jù)
2.獲取SurfaceView的SurfaceHolder并監(jiān)聽Surface的生命周期
3.在Surface創(chuàng)建后開啟相機,設(shè)置相機參數(shù),開啟預(yù)覽(設(shè)置預(yù)覽的旋轉(zhuǎn)方向)
4.拍照(旋轉(zhuǎn)或鏡像旋轉(zhuǎn),保存照片),或切換相機
5.釋放相機資源

下面貼出Camera工具類,每個方法都有注解:

/**
 * Camera1的工具類
 * 思路:創(chuàng)建SurfaceView或TextureView接收預(yù)覽數(shù)據(jù),
 * 獲取SurfaceHolder并監(jiān)聽Surface的生命周期,
 * 在Surface創(chuàng)建后開啟相機,設(shè)置相機參數(shù),開啟預(yù)覽(設(shè)置預(yù)覽的旋轉(zhuǎn)方向),
 * 拍照(旋轉(zhuǎn)或鏡像旋轉(zhuǎn),保存照片),切換相機,
 * 最后釋放相機資源
 */
public class Camera1Helper implements Camera.PreviewCallback, OrientationListener.OnOrientationListener {

    private static final String TAG = "CameraHelper";

    /**
     * 攝像頭方向,默認后置攝像頭
     */
    private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
    /**
     * 預(yù)覽旋轉(zhuǎn)的角度
     */
    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;
    //當前方向角度監(jiān)聽
    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);
    }

    /**
     * 第一步:監(jiān)聽Surface的生命周期
     * 開啟相機
     * 銷毀相機
     */
    public void init() {
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (mCamera == null) {
                    openCamera();  //打開相機
                }
                startPreview();  //開始預(yù)覽
            }

            @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);
        //獲取支持的預(yù)覽尺寸
        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) {
        //設(shè)置對焦模式
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
//        }
        //個別機型對攝像頭的預(yù)覽尺寸要求嚴格,需要指定尺寸
        setParameter(parameters);
    }

    /**
     * 第四步:開啟預(yù)覽
     */
    private void startPreview() {
        try {
            mCamera.setPreviewDisplay(surfaceHolder);
            //設(shè)置預(yù)覽時相機的旋轉(zhuǎn)角度
            setCameraDisplayOrientation();
            mCamera.startPreview();
            isActivedPreview = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 第三步:設(shè)置預(yù)覽角度,setDisplayOrientation本身只能改變預(yù)覽的角度
     * previewFrameCallback以及拍攝出來的照片是不會發(fā)生改變的,拍攝出來的照片角度依舊不正常的
     * 拍攝的照片需要自行處理
     * 這里Nexus5X的相機簡直沒法吐槽,后置攝像頭倒置了,切換攝像頭之后就出現(xiàn)問題了。
     * 原文鏈接: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
     * 指定相機的預(yù)覽尺寸和照片尺寸
     */
    private void setParameter(Camera.Parameters parameters) {
        try {
            //個別機型在SupportPreviewSizes里匯報了支持某種預(yù)覽尺寸,但實際是不支持的,設(shè)置進去就會拋出RuntimeException.
            mCamera.setParameters(parameters);
        } catch (Exception e) {
            e.printStackTrace();
            try {
                //遇到上面所說的情況,只能設(shè)置一個最小的預(yù)覽尺寸
                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;
    }

    /**
     * 獲取最佳的預(yù)覽尺寸
     *
     * @param width                 指定的預(yù)覽寬度
     * @param height                指定的預(yù)覽高度
     * @param supportedPreviewSizes 相機支持的預(yù)覽尺寸
     * @return
     */
    private Camera.Size getBestSize(int width, int height, List<Camera.Size> supportedPreviewSizes) {
        Camera.Size bestSize = null;
        //指定預(yù)覽的寬高比
        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;
            }
        });
    }

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

    /**
     * 轉(zhuǎn)換對焦區(qū)域
     * 范圍(-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;
        //計算對焦區(qū)域
        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
        }
        //首先保存原來的對焦模式,然后設(shè)置為macro,對焦回調(diào)后設(shè)置為保存的對焦模式
        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) {
                    //回調(diào)后 還原模式
                    Camera.Parameters params = camera.getParameters();
                    params.setFocusMode(currentFocusMode);
                    camera.setParameters(params);
                    if (success) {
                        Toast.makeText(activity, "對焦區(qū)域?qū)钩晒?, 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();
                                //利用傳感器獲取當前屏幕的角度加上預(yù)覽的角度
                                int rotation = /*(*/mDisplayOrientation /*+ mSensorRotation) % 360*/;
                                if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                                    //如果時后置攝像頭,則直接旋轉(zhuǎn)為特定的角度
                                    matrix.setRotate(rotation);
                                } else if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                                    //如果時前置攝像頭,鏡像;因為時鏡像,所以旋轉(zhuǎn)角度是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, "圖片數(shù)據(jù)無效");
                            }
                        }
                    }
                }).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軸方向加速度,從左側(cè)向右側(cè)移動,values[0]為負值;從右向左移動,values[0]為正值
        //y是values[1]的值,Y軸方向加速度,從上到下移動,values[1]為負值;從下往上移動,values[1]為正值
        //不考慮Z軸上的數(shù)據(jù),
        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;
    }

    /**
     * 在此處接收相機的預(yù)覽數(shù)據(jù)
     *
     * @param data   相機的預(yù)覽數(shù)據(jù)
     * @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中的調(diào)用

 @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();
        }
    }
在設(shè)置Camera的配置的時候,圖像數(shù)據(jù)類型支持多種類型,設(shè)置成parameters.setPreviewFormat(ImageFormat.JPEG);預(yù)覽時輸出的圖像就是JPEG的數(shù)據(jù)
預(yù)覽的圖像數(shù)據(jù)來源于硬件圖像傳感器,圖像傳感器固定在手機上,所以方向是固定的,一般手機的后置攝像頭是橫向固定相對于手持手機豎直方向旋轉(zhuǎn)90度。前置攝像頭剛好相反,獲取的數(shù)據(jù)需要鏡像處理

推薦文章:Android Camera預(yù)覽角度和拍照保存圖片角度學習
Android Orientation Sensor監(jiān)聽方向

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容