The use of custom camera Camera in Android

In the application, we often encounter the need to create a photo upload function, but our general approach is to directly adjust the system's camera interface or use an interface provided by a third party. What should we do if users have customized requirements or we have some of our own needs? Today we follow the steps to complete the production of a custom camera.

1.Camera related API

We mainly use two classes for taking pictures, one is SurfaceView, which we introduced last time; the other is Camera. So we need to learn about Camera related APIs.

  • getNumberOfCameras: Get the number of cameras of this device.
  • open: Open the camera, the rear camera is turned on by default. If there are multiple cameras, then open(0) means to open the rear camera, and open(1) means to open the front camera.
  • getParameters: Get the camera parameters and return the Camera.Parameters object.
  • setParameters: Set the camera parameters for taking pictures. The specific camera parameters are set by calling the following methods of Camera.Parameters.

setPreviewSize

Set preview interface size
setPictureSize Set the size of the saved picture.
setPictureFormat Set the picture format. Generally, ImageFormat.JPEG is used to represent the JPG format.
setFocusMode Set the focus mode. The value Camera.Parameters.FOCUS_MODE_AUTO will only focus once; the value FOCUS_MODE_CONTINUOUS_PICTURE will focus continuously
  • setPreviewDisplay: Set the surface holder of the preview interface, that is, the SurfaceHolder object. This method must be called in the surfaceCreated method of SurfaceHolder.Callback.
  • startPreview: Start preview. This method must be called after the setPreviewDisplay method.
  • unlock: The camera needs to be unlocked when recording, so that the camera can continue to record. This method must be called after the startPreview method.
  • setDisplayOrientation: Set the angle of the preview. Android's 0 degree is at three o'clock in the horizontal position, while the mobile phone screen is in the vertical position, and needs to be rotated 90 degrees from the horizontal position to the vertical position.
  • autoFocus: Set the focus event. The onAutoFocus method of the parameter autofocus interface AutoFocusCallback is triggered when the focus is completed, and the user is prompted to take a picture after the focus is completed.
  • takePicture: Start taking a picture and set up related events for taking pictures. The first parameter is the shutter callback interface ShutterCallback, its onShutter method is triggered when the shutter is pressed, and the sound of the photo can usually be played here, the default is "click"; the second parameter PictureCallback represents the callback interface of the original image, usually There is no need to deal with directly passing null; the third parameter PictureCallback represents the callback interface of JPG images, and the compressed image data can be obtained in the onPictureTaken method of this interface.
  • setZoomChangeListener: Set the zoom ratio change event. The onZoomChange method of the zoom change listener OnZoomChangeListener is triggered when the zoom ratio changes.
  • setPreviewCallback: Set the preview callback event, usually called during continuous shooting. The onPreviewFrame method of the preview callback interface PreviewCallback is triggered when the preview image changes.
  • stopPreview: Stop preview.
  • lock: Lock the camera after recording. This method is called after the stopPreview method.
  • release: Release the camera. Because the camera cannot be opened repeatedly, you must release the camera every time you exit the camera.

2. The code sets the surface view SurfaceView

Then we step by step to realize our function. First of all, we have to do a good job of surface view.

First of all, our layout is like this.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <Button
        android:id="@+id/bt_take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="拍照"/>
</RelativeLayout>

There are two elements in it, one is our surface view SurfaceView, and the other is our camera button.

Below is the code of our Activity

public class MainActivity extends WaterPermissionActivity<MainModel>
        implements MainCallback, View.OnClickListener {

    private Button bt_take_photo;
    private SurfaceView surfaceView;

    @Override
    protected MainModel getModelImp() {
        return new MainModel(this,this);
    }

    @Override
    protected int getContentLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initWidget() {
        bt_take_photo = findViewById(R.id.bt_take_photo);
        surfaceView = findViewById(R.id.surfaceView);
        bt_take_photo.setOnClickListener(this);
        //获取表面视图的表面持有者
        SurfaceHolder holder = surfaceView.getHolder();
        //给表面持有者添加表面变更监听器
        holder.addCallback(mSurfaceCallback);
        //去除黑色背景,TRANSLUCENT半透明,TRANSPARENT透明
        holder.setFormat(PixelFormat.TRANSPARENT);
        requestPermission(CAMERA);
    }

    @Override
    protected void doCamera() {
        requestPermission(READ_EXTERNAL_STORAGE);
    }

    @Override
    protected void doSDRead() {
        requestPermission(WRITE_EXTERNAL_STORAGE);
    }

    @Override
    protected void doSDWrite() {
        
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bt_take_photo:
                //点击拍照

                break;
        }
    }

    /**
     * 表面变更监听器
     */
    private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {

        }

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

        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

        }
    };
}

MainActivity, MainCallback, MainModel are my MVC framework structure, don't care about them. If you want to know about students, you can read my previous blog or leave a message to ask me. The code also contains the content of our request for dynamic permissions, the old rules, you can use your own.

The core code has two parts, the first part

//获取表面视图的表面持有者
SurfaceHolder holder = surfaceView.getHolder();
//给表面持有者添加表面变更监听器
holder.addCallback(mSurfaceCallback);
//去除黑色背景,TRANSLUCENT半透明,TRANSPARENT透明
holder.setFormat(PixelFormat.TRANSPARENT);

The second part is the definition of mSurfaceCallback in the first part

private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {

        }

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

        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

        }
    };

Don't forget the permissions configured in the manifest file

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

At this point, our surface view is set.

3. Display the screen shot by the camera on SurfaceView

First of all, we introduce a tool class for camera operation, we will use it later

public class CameraUtil {

    private static final Pattern COMMA_PATTERN = Pattern.compile(",");

    public static Point getSize(Context ctx) {
        WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        Point size = new Point();
        size.x = dm.widthPixels;
        size.y = dm.heightPixels;
        return size;
    }

    public static Point getCameraSize(Camera.Parameters params, Point screenSize) {
        String previewSizeValueString = params.get("preview-size-values");
        if (previewSizeValueString == null) {
            previewSizeValueString = params.get("preview-size-value");
        }
        Point cameraSize = null;
        if (previewSizeValueString != null) {
            cameraSize = findBestPreviewSizeValue(previewSizeValueString, screenSize);
        }
        if (cameraSize == null) {
            cameraSize = new Point((screenSize.x >> 3) << 3, (screenSize.y >> 3) << 3);
        }
        return cameraSize;
    }

    private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenSize) {
        int bestX = 0;
        int bestY = 0;
        int diff = Integer.MAX_VALUE;
        for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
            previewSize = previewSize.trim();
            int dimPosition = previewSize.indexOf('x');
            if (dimPosition < 0) {
                continue;
            }

            int newX;
            int newY;
            try {
                newX = Integer.parseInt(previewSize.substring(0, dimPosition));
                newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
            } catch (NumberFormatException nfe) {
                continue;
            }

            int newDiff = Math.abs((newX - screenSize.x) + (newY - screenSize.y));
            if (newDiff == 0) {
                bestX = newX;
                bestY = newY;
                break;
            } else if (newDiff < diff) {
                bestX = newX;
                bestY = newY;
                diff = newDiff;
            }
        }

        if (bestX > 0 && bestY > 0) {
            return new Point(bestX, bestY);
        }
        return null;
    }
}

Then we added the following member variables in the MainActivity just now

private Camera mCamera;//声明一个相机对象
private boolean isPreviewing;//是否正在预览
private Point mCameraSize;//相机画面的尺寸
private int mCameraType = 0;//设置前置还是后置 0:后置  1:前置
public static int CAMERA_BEHIND = 0; // 后置摄像头
public static int CAMERA_FRONT = 1; // 前置摄像头

In SurfaceHolder.Callback we add the following code

    /**
     * 表面变更监听器
     */
    private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
            //打开摄像头
            mCamera = Camera.open(mCameraType);
            try {
                //设置相机的预览页面
                mCamera.setPreviewDisplay(holder);
                //获得相机画面的尺寸
                mCameraSize = CameraUtil.getCameraSize(mCamera.getParameters()
                        , CameraUtil.getSize(MainActivity.this));
                //获取相机的参数信息
                Camera.Parameters parameters = mCamera.getParameters();
                //设置预览界面的尺寸
                parameters.setPreviewSize(1920, 1080);
                //设置图片的分辨率
                parameters.setPictureSize(1920, 1080);
                parameters.setJpegQuality(100); // 设置照片质量
                //设置图片的格式
                parameters.setPictureFormat(ImageFormat.JPEG);
                //设置对焦模式为自动对焦。前置摄像头似乎无法自动对焦
                if (mCameraType == CAMERA_BEHIND) {
                    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                }
                //设置相机的参数信息
                mCamera.setParameters(parameters);
            } catch (IOException e) {
                e.printStackTrace();
                mCamera.release();//遇到异常要释放相机资源
                mCamera = null;
            }
        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
            //设置相机的展示角度
            mCamera.setDisplayOrientation(90);
            //开始预览画面
            mCamera.startPreview();
            isPreviewing = true;
            //开始自动对焦
            mCamera.autoFocus(null);
            //设置相机的预览监听器。注意这里的setPreviewCallback给连拍功能用
//            mCamera.setPreviewCallback(mPr);
        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
            //将预览监听器置空
            mCamera.setPreviewCallback(null);
            //停止预览画面
            mCamera.stopPreview();
            //释放相机资源
            mCamera.release();
            mCamera = null;
        }
    };

In this way, we will find that SurfaceView shows our image when we run it.

4. Take pictures

Next we are about to enter our key function to take pictures.

The first is the click event of our camera button. Here we let it call a method

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.bt_take_photo:
            //点击拍照
            doTakePicture();
            break;
    }
}

The implementation of the camera method is as follows

private void doTakePicture(){
    if (isPreviewing && mCamera!=null){
        //命令相机拍摄一张照片
        mCamera.takePicture(mShutterCallback,null,mPictureCallback);
    }
}

Two objects are used here, mShutterCallback is the object we implemented to monitor by pressing the shutter, and mPictureCallback is the callback of the result of our photo.

Implementation of mShutterCallback

private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
    @Override
    public void onShutter() {
            
    }
};

This is a monitoring after we press the shutter, we can let the program play a click sound here, or perform some other operations.

Implementation of mPictureCallback

private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Bitmap raw = null;
            if (null != data) {
                //原始图像数据data是字节数组,需要将其解析成位图
                raw = BitmapFactory.decodeByteArray(data, 0, data.length);
                //停止预览画面
                mCamera.stopPreview();
                isPreviewing = false;
            }
            //旋转位图
            Bitmap bitmap = getRotateBitmap(raw
                    , (mCameraType == CAMERA_BEHIND) ? 90 : -90);
            //获取本次拍摄的照片保存路径
            List<String> listPath = new ArrayList<>();
            listPath.add("myCamera");
            listPath.add("photos");
            mPhotoPath = PathGetUtil.getLongwayPath(MainActivity.this, listPath);
            File fileDir = new File(mPhotoPath);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            File filePic = new File(mPhotoPath, "ww" + System.currentTimeMillis() + ".jpg");
            if (!filePic.exists()) {
                try {
                    filePic.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            saveImage(filePic.getPath(), bitmap);
            //保存文件需要时间
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //再次进入预览画面
            mCamera.startPreview();
            isPreviewing = true;
        }
    };

Among them, we use a tool method for rotating bitmaps, the code is as follows

// 获得旋转角度之后的位图对象
public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree) {
    // 创建操作图片用的矩阵对象
    Matrix matrix = new Matrix();
    // 执行图片的旋转动作
    matrix.postRotate(rotateDegree);
    // 创建并返回旋转后的位图对象
    return Bitmap.createBitmap(b, 0, 0, b.getWidth(),b.getHeight(), matrix, false);
}

In the member variable, we add a photo storage path

private String mPhotoPath;//照片的保存路径

Operating Bitmap related methods

    public static void saveImage(String path, Bitmap bitmap){
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
            bitmap.compress(Bitmap.CompressFormat.JPEG,80,bos);
            bos.flush();
            bos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

In this way, a custom camera function is completed.

ps: For current mobile phones, Camera can be said to be outdated. I use Android 9.0 mobile phone test, the shooting effect is not satisfactory. The preview size and photo size we set is already the maximum supported 1920*1080, so it is obviously not very suitable for current mobile phones. However, as a developer, we need to have an understanding of this technology, and understand a basic implementation. This can be a good help for us in the future study of CameraX provided in Camera2 and Jetpack.

Guess you like

Origin blog.csdn.net/weixin_38322371/article/details/115083565