android Camera 开发

2020-12-01  本文已影响0人  谦谦行者

注:下面例子中,scanView是一个自定义view,背景透明,需要放在预览的中间位置,否则自动对焦不准,设置对焦区域还不会
自定义相机拍照
本文参考:https://juejin.cn/post/6844903534610087943#heading-3
在android自定义camera需要根据android版本使用不同的api
如下图:

image.png
在对应版本使用对应api,才能兼容大多数设备
另外,与上图不同的是,使用CameraX 替代 Camera2。
CameraX 拥有比Camera2更好的兼容性,但是最低支持api21 所以在android 21 之前仍然使用Camera1

Camera1+SurfaceView

  1. 获取摄像头Camera1对象
  2. 设置 SurfaceHolder.addCallback
  3. 在回调的 surfaceCreated() 中设置camera 参数,调用camera.startPreview() 开启预览
  4. 拍照

1.获取摄像头Camera1对象

val numberOfCameras = Camera.getNumberOfCameras()
        for (index in 0 until numberOfCameras) {
            val cameraInfo = Camera.CameraInfo()
            Camera.getCameraInfo(index, cameraInfo)
            // 前置摄像头
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                frontCameraId = index
                frontCameraOrientation = cameraInfo.orientation
                // 后置摄像头
            }else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                backCameraId = index
                backCameraOrientation = cameraInfo.orientation
            }
        }

2. 设置 SurfaceHolder.addCallback

sv.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder?) {
                camera = Camera.open(backCameraId)
                // 设置摄像头参数,开启预览
                startPreview(sv.holder)
            }

            override fun surfaceChanged(
                holder: SurfaceHolder?,
                format: Int,
                width: Int,
                height: Int,
            ) {
            }

            override fun surfaceDestroyed(holder: SurfaceHolder?) {
            }
        })

3. 设置camera参数,开启预览

fun startPreview(surfaceHolder: SurfaceHolder){
        // 获取相机参数
        val params = camera.parameters
        // 设置自动对焦
        params.focusMode = Camera.Parameters.FOCUS_MODE_AUTO // 自动对焦

        // 设置闪光模式,自动模式
        params.flashMode = Camera.Parameters.FLASH_MODE_AUTO // 自动模式,当光线较暗时自动打开闪光灯

        val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val display = windowManager.defaultDisplay
        val screenResolution = getDisplaySize(display)
        // 获取手机最佳预览尺寸
        val point = CameraConfigurationUtils.findBestPreviewSizeValue(params, screenResolution)
        isRotate = point.x > point.y
        val rotation = windowManager.defaultDisplay.rotation
        var degrees = 0
        when (rotation) {
            Surface.ROTATION_0 -> {
                degrees = 0
            }
            Surface.ROTATION_90 -> {
                degrees = 90
            }
            Surface.ROTATION_180 -> {
                degrees = 180
            }
            Surface.ROTATION_270 -> {
                degrees = 270
            }
        }
        val previewSizes = params.supportedPictureSizes
        previewSizes.forEach {
            Log.e("预览尺寸", "width:${it.width} height:${it.height}")
        }
        // 设置预览方向(垂直)
        camera.setDisplayOrientation((backCameraOrientation - degrees + 360) % 360)
        params.setPreviewSize(point.x, point.y)
        camera.parameters = params
        camera.setPreviewDisplay(surfaceHolder)

        camera.startPreview()
        // 只有加上下面的代码才能自动对焦,只在param设置自动对焦是没用的
        camera.autoFocus { success, camera ->
            if (success) {
                camera.cancelAutoFocus()//只有加上了这一句,才会自动对焦
                doAutoFocus()
            }
        }

    }


    //设置聚焦
    private fun doAutoFocus() {
        var parameters = camera.parameters
        parameters.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
        camera.parameters = parameters
        camera.autoFocus(AutoFocusCallback { success, camera ->
            if (success) {
                camera.cancelAutoFocus() // 只有加上了这一句,才会自动对焦。
                if (Build.MODEL != "KORIDY H30") {
                    parameters = camera.parameters
                    parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE // 连续自动对焦
                    camera.parameters = parameters
                } else {
                    parameters = camera.parameters
                    parameters.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
                    camera.parameters = parameters
                }
            }
        })
    }
// 获取当前手机屏幕的尺寸
private fun getDisplaySize(display: Display): Point {
        val point = Point()
        try {
            display.getSize(point)
        } catch (ignore: NoSuchMethodError) {
            point.x = display.width
            point.y = display.height
        }
        return point
    }

获取预览最佳尺寸的工具类

public final class CameraConfigurationUtils {

    private static final String TAG = "CameraConfiguration";

    private static final int MIN_PREVIEW_PIXELS = 480 * 320; // normal screen
    private static final double MAX_ASPECT_DISTORTION = 0.15;

    public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {

        List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
        if (rawSupportedSizes == null) {
            Log.w(TAG, "Device returned no supported preview sizes; using default");
            Camera.Size defaultSize = parameters.getPreviewSize();
            if (defaultSize == null) {
                throw new IllegalStateException("Parameters contained no preview size!");
            }
            return new Point(defaultSize.width, defaultSize.height);
        }

        if (Log.isLoggable(TAG, Log.INFO)) {
            StringBuilder previewSizesString = new StringBuilder();
            for (Camera.Size size : rawSupportedSizes) {
                previewSizesString.append(size.width).append('x').append(size.height).append(' ');
            }
            Log.i(TAG, "Supported preview sizes: " + previewSizesString);
        }

//        double screenAspectRatio = screenResolution.x / (double) screenResolution.y;

        // Find a suitable size, with max resolution
//        int maxResolution = 0;
        Camera.Size maxResPreviewSize = null;
        int diff = Integer.MAX_VALUE;
        for (Camera.Size size : rawSupportedSizes) {
            int realWidth = size.width;
            int realHeight = size.height;
            int resolution = realWidth * realHeight;
            if (resolution < MIN_PREVIEW_PIXELS) {
                continue;
            }

            boolean isCandidatePortrait = realWidth < realHeight;
            int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
            int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
//            double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight;
//            double distortion = Math.abs(aspectRatio - screenAspectRatio);
//            if (distortion > MAX_ASPECT_DISTORTION) {
//                continue;
//            }

            if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
                Point exactPoint = new Point(realWidth, realHeight);
                Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
                return exactPoint;
            }

            int newDiff = Math.abs(maybeFlippedWidth - screenResolution.x) + Math.abs(maybeFlippedHeight - screenResolution.y);
            if (newDiff < diff) {
                maxResPreviewSize = size;
                diff = newDiff;
            }

            // Resolution is suitable; record the one with max resolution
//            if (resolution > maxResolution) {
//                maxResolution = resolution;
//                maxResPreviewSize = size;
//            }
        }

        // If no exact match, use largest preview size. This was not a great idea on older devices because
        // of the additional computation needed. We're likely to get here on newer Android 4+ devices, where
        // the CPU is much more powerful.
        if (maxResPreviewSize != null) {
            Point largestSize = new Point(maxResPreviewSize.width, maxResPreviewSize.height);
            Log.i(TAG, "Using largest suitable preview size: " + largestSize);
            return largestSize;
        }

        // If there is nothing at all suitable, return current preview size
        Camera.Size defaultPreview = parameters.getPreviewSize();
        if (defaultPreview == null) {
            throw new IllegalStateException("Parameters contained no preview size!");
        }
        Point defaultSize = new Point(defaultPreview.width, defaultPreview.height);
        Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
        return defaultSize;
    }


}

4.拍照

调用camera.takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback jpeg)
只实现第三个回调即可

camera.takePicture(null, null, { data, camera ->
                // 创建存放照片的文件
                val file =
                    File("${getExternalStorageDirectory().path}/${System.currentTimeMillis()}test_photo.jpg")
                val result = FileUtils.createFileByDeleteOldFile(file)
                // Tools的getFocusedBitmap 是根据传入的Rect,截取对应区域的照片生成一个bitmap
                // scan_view 是一个自定义view,画了一个扫描框,注意获取getRect()时
                // 要穿scan_view 在相对于父布局的位置
                val bitmap = Tools.getFocusedBitmap(this, camera, data, scan_view.getRect())

                // 把图片存在文件里
                FileIOUtils.writeFileFromBytesByStream(file, ImageUtils.bitmap2Bytes(bitmap,
                    Bitmap.CompressFormat.JPEG))
                camera?.startPreview()
            })

Tools工具类

public class Tools {

    public static Bitmap rotateBitmap(Bitmap source, float angle) {
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
    }

    public static Bitmap preRotateBitmap(Bitmap source, float angle) {
        Matrix matrix = new Matrix();
        matrix.preRotate(angle);
        return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, false);
    }

    public enum ScalingLogic {
        CROP, FIT
    }

    public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
                                          ScalingLogic scalingLogic) {
        if (scalingLogic == ScalingLogic.FIT) {
            final float srcAspect = (float) srcWidth / (float) srcHeight;
            final float dstAspect = (float) dstWidth / (float) dstHeight;

            if (srcAspect > dstAspect) {
                return srcWidth / dstWidth;
            } else {
                return srcHeight / dstHeight;
            }
        } else {
            final float srcAspect = (float) srcWidth / (float) srcHeight;
            final float dstAspect = (float) dstWidth / (float) dstHeight;

            if (srcAspect > dstAspect) {
                return srcHeight / dstHeight;
            } else {
                return srcWidth / dstWidth;
            }
        }
    }

    public static Bitmap decodeByteArray(byte[] bytes, int dstWidth, int dstHeight,
                                        ScalingLogic scalingLogic) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        options.inJustDecodeBounds = false;
        options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth,
                dstHeight, scalingLogic);
        Bitmap unscaledBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);

        return unscaledBitmap;
    }

    public static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
                                        ScalingLogic scalingLogic) {
        if (scalingLogic == ScalingLogic.CROP) {
            final float srcAspect = (float) srcWidth / (float) srcHeight;
            final float dstAspect = (float) dstWidth / (float) dstHeight;

            if (srcAspect > dstAspect) {
                final int srcRectWidth = (int) (srcHeight * dstAspect);
                final int srcRectLeft = (srcWidth - srcRectWidth) / 2;
                return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight);
            } else {
                final int srcRectHeight = (int) (srcWidth / dstAspect);
                final int scrRectTop = (int) (srcHeight - srcRectHeight) / 2;
                return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight);
            }
        } else {
            return new Rect(0, 0, srcWidth, srcHeight);
        }
    }

    public static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
                                        ScalingLogic scalingLogic) {
        if (scalingLogic == ScalingLogic.FIT) {
            final float srcAspect = (float) srcWidth / (float) srcHeight;
            final float dstAspect = (float) dstWidth / (float) dstHeight;

            if (srcAspect > dstAspect) {
                return new Rect(0, 0, dstWidth, (int) (dstWidth / srcAspect));
            } else {
                return new Rect(0, 0, (int) (dstHeight * srcAspect), dstHeight);
            }
        } else {
            return new Rect(0, 0, dstWidth, dstHeight);
        }
    }

    public static Bitmap createScaledBitmap(Bitmap unscaledBitmap, int dstWidth, int dstHeight,
                                            ScalingLogic scalingLogic) {
        Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(),
                dstWidth, dstHeight, scalingLogic);
        Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(),
                dstWidth, dstHeight, scalingLogic);
        Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(scaledBitmap);
        canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG));

        return scaledBitmap;
    }
    public static Point getScreenResolution(Context context) {

        WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = manager.getDefaultDisplay();

        int width = display.getWidth();
        int height = display.getHeight();
        return new Point(width, height);
    }
    // 根据相机拍照原图,生成符合Rect尺寸的压缩过的图片
    public static Bitmap getFocusedBitmap(Context context, Camera camera, byte[] data, Rect box){
        Point ScrRes = getScreenResolution(context);
        Point CamRes = CameraConfigurationUtils.findBestPreviewSizeValue(camera.getParameters(), ScrRes);

        int SW = ScrRes.x;
        int SH = ScrRes.y;

        int RW = box.width();
        int RH = box.height();
        int RL = box.left;
        int RT = box.top;

        float RSW = (float) (RW * Math.pow(SW, -1));
        float RSH = (float) (RH * Math.pow(SH, -1));

        float RSL = (float) (RL * Math.pow(SW, -1));
        float RST = (float) (RT * Math.pow(SH, -1));

        float k = 0.5f;

        int CW = CamRes.x;
        int CH = CamRes.y;

        int X = (int) (k * CW);
        int Y = (int) (k * CH);

        Bitmap unscaledBitmap = Tools.decodeByteArray(data, X, Y, ScalingLogic.CROP);
        // 压缩图片,会降低清晰度
        Bitmap bmp = Tools.createScaledBitmap(unscaledBitmap, X, Y, ScalingLogic.CROP);
        unscaledBitmap.recycle();

        if (CW > CH){
            bmp = Tools.rotateBitmap(bmp, 90);
        }else {
            String phone = Build.BRAND + Build.MODEL;
            if(phone.equals("RedmiM2004J7AC")){
                bmp = Tools.rotateBitmap(bmp, 90);
            }
        }

        int BW = bmp.getWidth();
        int BH = bmp.getHeight();

        int RBL = (int) (RSL * BW);
        int RBT = (int) (RST * BH);

        int RBW = (int) (RSW * BW);
        int RBH = (int) (RSH * BH);

        Bitmap res = Bitmap.createBitmap(bmp, RBL, RBT, RBW, RBH);
        bmp.recycle();

        return res;
    }

//    private static Pattern pattern = Pattern.compile("(1|861)\\d{10}$*");
    private static Pattern pattern = Pattern.compile("[0-9]{13}[D-F]");

    private static StringBuilder bf = new StringBuilder();

    public static String getTelNum(String sParam){
        if(TextUtils.isEmpty(sParam)){
            return "";
        }

        Matcher matcher = pattern.matcher(sParam.trim());
        bf.delete(0, bf.length());
        Log.e("test", sParam);
        while (matcher.find()) {
            bf.append(matcher.group()).append("\n");
        }
        int len = bf.length();
        if (len > 0) {
            bf.deleteCharAt(len - 1);
        }
        Log.e("test", bf.toString());
        return bf.toString();
    }
}

Camera1+TextureView

与SurfaceView不同的是,需要设置TextureView.surfaceTextureListener
在onSurfaceTextureAvailable() 中初始化设置camera 的参数
并且调用camera.setPreviewTexture(surfaceTexture) 替换camera.setPreviewDisplay(surfaceHolder)

最后在不用的时候,调用camera.stopPreview()、camera.release() 停止预览和释放相机资源

上一篇 下一篇

猜你喜欢

热点阅读