Android Camera1相机预览

实现效果

相机预览

相机预览.gif

切换相机

切换相机.gif

一、Android相机API

目前Android中有三种API,供开发者对相机进行操作

  • Camera1
  • Camera2
  • CameraX

本文重点在于说明Camera1的使用

二、CameraManager

可以对相机的使用进行封装,只需要定义相机的相关操作,具体实现是Camera1,或Camera2,亦或是CameraX,都无需外部进行修改,只需内部进行定义,首先,先定义相机的相关操作接口ICamera

相机接口

public interface ICamera {
    /**
     * 打开相机
     */
    void openCamera(Activity activity, SurfaceTexture surfaceTexture);
    /**
     * 打开相机
     */
    void openCamera(int facing, Activity activity, SurfaceTexture surfaceTexture);
    /**
     * 关闭相机
     */
    void closeCamera();
    /**
     * 切换相机
     */
    void switchCamera();
    /**
     * 切换相机
     */
    void switchCamera(int facing);
    /**
     * 设置Facing
     */
    void setCameraFacing(int facing);
    /**
     * 获取Facing
     */
    int getCameraFacing();
    /**
     * 设置预览尺寸
     */
    void setPreviewSize(Size cameraSize);
    /**
     * 获取预览尺寸
     */
    Size getPreviewSize();
    /**
     * 设置显示旋转角度
     */
    void setDisplayOrientation(int displayOrientation);
    /**
     * 获取显示旋转角度
     */
    int getDisplayOrientation();
    /**
     * 释放相机
     */
    void releaseCamera();
    /**
     * 添加相机回调
     */
    void addOnCameraListener(OnCameraListener onCameraListener);
    /**
     * 移除相机回调
     */
    void removeOnCameraListener(OnCameraListener onCameraListener);
    /**
     * 移除所有回调
     */
    void removeAllOnCameraListener();
}
复制代码

注意到,里面有个相机回调OnCameraListener,这是自定义的一个相机回调

public interface OnCameraListener {
    /**
     * 相机打开
     */
    void onCameraOpened(Size cameraSize, int facing);

    /**
     * 相机关闭
     */
    void onCameraClosed();

    /**
     * 相机异常
     */
    void onCameraError(Exception e);
}
复制代码

接下来,我们新建CameraManager,实现ICamera接口

public class CameraManager implements ICamera {
    /**
     * Camera实现
     */
    private final ICamera camera = new Camera1();

    /**
     * 后台线程
     */
    private Handler handler;
    private HandlerThread thread;

    @Override
    public void openCamera(Activity activity, SurfaceTexture surfaceTexture) {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.openCamera(activity, surfaceTexture);
            }
        });
    }

    @Override
    public void openCamera(int facing, Activity activity, SurfaceTexture surfaceTexture) {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.openCamera(facing, activity, surfaceTexture);
            }
        });
    }

    @Override
    public void closeCamera() {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.closeCamera();
            }
        });
    }

    @Override
    public void switchCamera() {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.switchCamera();
            }
        });
    }

    @Override
    public void switchCamera(int facing) {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.switchCamera(facing);
            }
        });
    }

    @Override
    public void setCameraFacing(int facing) {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.setCameraFacing(facing);
            }
        });
    }

    @Override
    public int getCameraFacing() {
        return camera.getCameraFacing();
    }

    @Override
    public void setPreviewSize(Size cameraSize) {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.setPreviewSize(cameraSize);
            }
        });
    }

    @Override
    public Size getPreviewSize() {
        return camera.getPreviewSize();
    }

    @Override
    public void setDisplayOrientation(int displayOrientation) {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.setDisplayOrientation(displayOrientation);
            }
        });
    }

    @Override
    public int getDisplayOrientation() {
        return camera.getDisplayOrientation();
    }

    @Override
    public void releaseCamera() {
        if (handler == null) {
            return;
        }
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.releaseCamera();
                stopBackground();
            }
        });
    }

    @Override
    public void addOnCameraListener(OnCameraListener onCameraListener) {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.addOnCameraListener(onCameraListener);
            }
        });
    }

    @Override
    public void removeOnCameraListener(OnCameraListener onCameraListener) {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.removeOnCameraListener(onCameraListener);
            }
        });
    }

    @Override
    public void removeAllOnCameraListener() {
        postTask(new Runnable() {
            @Override
            public void run() {
                camera.removeAllOnCameraListener();
            }
        });
    }

    /**
     * 获取Handler
     */
    private Handler getHandler() {
        if (thread == null || handler == null) {
            startBackground();
        }
        return handler;
    }

    /**
     * 开启线程
     */
    private void startBackground() {
        stopBackground();
        thread = new HandlerThread("camera_manager");
        thread.start();
        handler = new Handler(thread.getLooper());
    }

    /**
     * 关闭线程
     */
    private void stopBackground() {
        if (thread != null) {
            thread.quitSafely();
            thread = null;
        }
        if (handler != null) {
            handler = null;
        }
    }

    /**
     * 执行任务
     */
    private void postTask(Runnable runnable) {
        getHandler().post(runnable);
    }
}
复制代码

注意到,CameraManager内部开启了Handler,目的是让相机的相关操作都在子线程进行

接下来可以具体实现Camera1

同样,我们新建Camera1,并实现ICamera接口

三、Camera1

全局变量

/**
 * Camera
 */
private Camera camera;
/**
 * 预览尺寸
 */
private Size cameraSize = new Size(-1, -1);
/**
 * 默认摄像头方向
 */
private int facing = CameraConstants.facing.BACK;
/**
 * 旋转角度
 */
private int displayOrientation = -1;
复制代码

打开相机

public void openCamera(int facing, Activity activity, SurfaceTexture surfaceTexture) {
    // 先关闭相机
    closeCamera();
    // 判断是否存在摄像头
    int cameraNum = Camera.getNumberOfCameras();
    if (cameraNum <= 0) {
        onCameraError(new IllegalStateException("camera num <= 0"));
        return;
    }
    // 检查传入的facing
    int cameraIndex = -1;
    if (facing == CameraConstants.facing.BACK) {
        cameraIndex = Camera.CameraInfo.CAMERA_FACING_BACK;
    } else if (facing == CameraConstants.facing.FRONT) {
        cameraIndex = Camera.CameraInfo.CAMERA_FACING_FRONT;
    }
    if (cameraIndex == -1) {
        onCameraError(new IllegalStateException("camera facing exception"));
        return;
    }
    // 判断摄像头个数,以决定使用哪个打开方式
    if (cameraNum >= 2) {
        camera = Camera.open(cameraIndex);
    } else {
        camera = Camera.open();
    }
    // 判断Camera是否初始化成功
    if (camera == null) {
        onCameraError(new IllegalStateException("camera is null"));
        return;
    }
    this.facing = facing;
    try {
        // 获取摄像头参数
        Camera.Parameters parameters = camera.getParameters();
        List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
        if (cameraSize.getWidth() <= 0 || cameraSize.getHeight() <= 0) {
            Camera.Size size = findTheBestSize(previewSizeList,
                    activity.getResources().getDisplayMetrics().widthPixels,
                    activity.getResources().getDisplayMetrics().heightPixels);
            cameraSize.setWidth(size.width);
            cameraSize.setHeight(size.height);
        }
        // 设置预览尺寸
        parameters.setPreviewSize(cameraSize.getWidth(), cameraSize.getHeight());
        // 这里设置使用的对焦模式
        if (this.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        }
        // 设置摄像头参数
        camera.setParameters(parameters);
        camera.setPreviewTexture(surfaceTexture);
        if (displayOrientation < 0) {
            displayOrientation = CameraUtils.getDisplayOrientation(activity, cameraIndex);
        }
        camera.setDisplayOrientation(displayOrientation);
        camera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
            @Override
            public void onPreviewFrame(byte[] data, Camera camera) {
                onCameraOpened(cameraSize, Camera1.this.facing);
            }
        });
        camera.startPreview();
    } catch (IOException e) {
        onCameraError(e);
    }
}
复制代码

注意到,在对相机参数设置的时候,使用findTheBestSize方法进行选择最佳的尺寸,该方法不是Android原生提供的,是根据相机所支持的所有预览尺寸和当前屏幕的尺寸所计算得到的

private Camera.Size findTheBestSize(List<Camera.Size> sizeList, int screenW, int screenH) {
    if (sizeList == null || sizeList.isEmpty()) {
        throw new IllegalArgumentException();
    }
    Log.d(TAG, "findTheBestSize: screenW:" + screenW + " screenH:" + screenH);
    Camera.Size bestSize = sizeList.get(0);
    for (Camera.Size size : sizeList) {
        int width = size.height;
        int height = size.width;
        Log.d(TAG, "findTheBestSize: width:" + width + " height:" + height);
        float ratioW = (float) width / screenW;
        float ratioH = (float) height / screenH;
        Log.d(TAG, "findTheBestSize: ratioW:" + ratioW + " ratioH:" + ratioH);
        if (ratioW == ratioH) {
            bestSize = size;
            break;
        }
    }
    return bestSize;
}
复制代码

还注意到,设置相机旋转角度时,有用到CameraUtils.getDisplayOrientation方法,该方法大概长这样

public static int getDisplayOrientation(Activity activity, int facing) {
    Camera.CameraInfo info = new Camera.CameraInfo();
    Camera.getCameraInfo(facing, 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;  // compensate the mirror
    } else {  // back-facing
        result = (info.orientation - degrees + 360) % 360;
    }
    return result;
}
复制代码

关闭相机

public void closeCamera() {
    if (camera == null) {
        return;
    }
    camera.stopPreview();
    camera.release();
    camera = null;
    onCameraClosed();
}
复制代码

切换相机

public void switchCamera() {
    if (facing == CameraConstants.facing.BACK) {
        facing = CameraConstants.facing.FRONT;
    } else {
        facing = CameraConstants.facing.BACK;
    }
}
复制代码

注意到,切换相机方法并没有真正的去切换相机,而是改变全局变量facing的值,需要配合使用openCamera方法后,进行切换相机的操作

接下来就是真正实现相机预览

四、TextureView

可以配合TextureView使用,进行相机相关操作

首先在xml中定义TextureView

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/textureView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

</FrameLayout>
复制代码

Activity中定义CameraManager

private final CameraManager cameraManager = new CameraManager();
复制代码

打开相机

private void openCamera() {
    if (textureView.isAvailable()) {
        cameraManager.openCamera(TextureViewCameraActivity.this, textureView.getSurfaceTexture());
    } else {
        textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
                cameraManager.openCamera(TextureViewCameraActivity.this, surface);
            }
            @Override
            public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
            }
            @Override
            public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
                return true;
            }
            @Override
            public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
            }
        });
    }
}
复制代码

关闭相机

private void closeCamera() {
    cameraManager.closeCamera();
}
复制代码

openCamera,可以选择在ActivityonResume回调中调用,也可以在某个按钮点击后进行调用

closeCamera,可以选择在ActivityonPause回调中调用,也可以在某个按钮点击后进行调用

切换相机

private void switchCamera() {
    cameraManager.switchCamera();
    openCamera();
}
复制代码

切换相机,可以先调用cameraManager.switchCamera(),接着调用openCamera即可

五、SurfaceView

当然,我们也可以选择SurfaceView作为相机的预览的容器,我们只需要修改Camera1中的setPreviewTexture(surfaceTexure)setPreviewDisplay(surfaceHolder)即可

六、GitHub

以上相关代码都可以在下面找到

Camera相关实现代码

TextureViewCameraActivity

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享