Skip to content

使用Camera2 API开发一个相机Demo

Camera2介绍

从Android 5.0开始,Google引入了一套全新的相机框架Camera2,并且废弃了旧的相机框架Camera1。Camera2的出现给相机应用程序带来了巨大的变革,它给相机应用层提供了更多的相机控制权限。国内的众多手机厂商系统相机应用里形式多样的快拍、连拍、自动构图、延时摄影、自动构图等功能,本质上都是通过对Camera2 和HAL3对深入挖掘后构建出的高度复杂的应用程序。

Android设备和Camera器件是通过pipeline的概念将两者串联的,pipeline即一整套Camera处理流程,可以抽象成一个CaptureRequestSession过程,在这个过程中由Android系统发送CaptureRequest,Camera返回元数据MetaData来进行交互,预览和拍照等数据的传递时通过Surface进行。

截屏2024-03-26 22.27.41

Camera Framework

image-20231105114406287

以Activity生命周期的顺序作为切入点,使用Camera2开发一款相机Demo:

1、onCreate

在onCreate()时,主要做自定义相机工具类CameraUtils的初始化,相机初始化、视图初始化的工作;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 通过CameraUtils的初始化方法,给CameraUtils拿到CameraManager对象
    CameraUtils.init(this);
    // 初始化相机,获取CameraManager和CameraID、画面尺寸
    initCamera();
    // 初始化界面视图
    initViews();
}

1.1、相机工具类的初始化

自定义相机工具类CameraUtils的初始化主要是为了创建一个CameraManager对象mCameraManager;

public static void init(Context context){
    if(appContext == null){
        // 由于该方法是静态的,将上下文生命周期与应用程序绑定,防止内存泄露
        appContext = context.getApplicationContext();
        mCameraManager = (CameraManager) appContext.getSystemService(Context.CAMERA_SERVICE);
    }
}

1.2、相机初始化

相机初始化的目的主要是为了获取CameraId,以及根据CameraID,得到对应相机支持的所有画幅的尺寸outputSizes

private void initCamera(){
    // 拿到CameraManager对象mCameraManager
    mCameraManager = CameraUtils.getInstance().getCameraManager();
    // 获取后置镜头ID
    cameraId = CameraUtils.getInstance().getBackCameraId();
    // 根据后置镜头ID,获取支持的所有输出尺寸
    outputSizes = CameraUtils.getInstance().getCameraOutputSizes(cameraId, SurfaceTexture.class);
    // 获取第一个尺寸
    photoSize = outputSizes.get(0);
}

1.2.1、获取CameraId

public String getBackCameraId(){
    return getCameraId(false);
}

拿到后置摄像头的CameraID;

LENS_FACING_FRONT,指的是前置摄像头,CameraID一般为1;

LENS_FACING_BACK,指的是后置摄像头,CameraID一般为0;

public String getCameraId(boolean useFront){
    try {
        for (String cameraId: mCameraManager.getCameraIdList()) {
            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
            //获取当前CameraId对应相机的朝向
            int cameraFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
            if(useFront){
                if(cameraFacing == CameraCharacteristics.LENS_FACING_FRONT){
                    Log.d(TAG, "前置:getCameraId: "+cameraId+",cameraFacing:"+cameraFacing);
                    return cameraId;
                }
            }else {
                if(cameraFacing == CameraCharacteristics.LENS_FACING_BACK){
                  Log.d(TAG, "后置:getCameraId: "+cameraId+",cameraFacing:"+cameraFacing);  
                  return cameraId;
                }
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "getCameraId: 获取相机信息异常",e);
    }
    return null;
}

以笔者手上的一台Xiaomi 12 Pro为例,调用Camera2 APImCameraManager.getCameraIdList()返回的结果是[0,1],表示后摄和前摄的id;后摄characteristics.get(CameraCharacteristics.LENS_FACING)返回的结果是1,前摄返回的结果是0;

1.2.2、拿到对应Camera所支持的所有的outputSizes

首先通过CameraManager对象调用getCameraCharacteristics()方法,拿到对应CameraID的characteristics信息,然后使用characteristics调用get方法传入SCALER_STREAM_CONFIGURATION_MAP常量,拿到config信息;再通过config对象调用getOutputSizes(clz)方法得到该相机所支持的所有的画幅尺寸列表。

public List<Size> getCameraOutputSizes(String cameraId, Class clz){
    try {
        CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
        StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        List<Size> sizes = Arrays.asList(configs.getOutputSizes(clz));
        Collections.sort(sizes, new Comparator<Size>() {
            @Override
            public int compare(Size o1, Size o2) {
                return o2.getWidth() * o2.getHeight() - o1.getWidth() * o1.getHeight();
            }
        });
        return sizes;

    } catch (CameraAccessException e) {
        Log.e(TAG, "getCameraOutputSizes: 获取相机信息异常", e);
    }
    return null;
}

1.3、Activity视图初始化

加载Activity的布局文件,再对快门按键添加监听器,实现快门功能;

private void initViews() {
    // 设置页面布局
    setContentView(R.layout.activity_main);
    btnPhoto = findViewById(R.id.btn_photo);
    // 快门按钮设置监听
    btnPhoto.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 设置点击时间回调,触发拍照
            takePhoto();
        }
    });
    previewView = findViewById(R.id.preview_view);
}

1.4 拍照takePhoto

cameraDevice.createCaptureRequest()传入一个拍照的标志位即可返回一个CaptureRequest.Builder对象;cameraDevice.TEMPLATE_STILL_CAPTURE表示构建的请求为拍照。

参数 功能
TEMPLATE_PREVIEW 用于配置预览的模板
TEMPLATE_RECORD 用于视频录制的模板
TEMPLATE_STILL_CAPTURE 用于拍照的模板
TEMPLATE_VIDEO_SNAPSHOT 用于在录制视频过程中支持拍照的模板
TEMPLATE_MANUAL 用于自己手动配置大部分参数的模板
private void takePhoto() {
    try {
        photoRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        cameraOrientation = PHOTO_ORI.get(displayRotation);
        photoRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION,cameraOrientation);
        photoRequestBuilder.addTarget(photoSurface);
        photoRequest = photoRequestBuilder.build();
        captureSession.stopRepeating();
        captureSession.capture(photoRequest,sessionCaptureCallback,null);
        // 启动一个新线程来播放声音
        new Thread(this::shutterSound).start();

    } catch (CameraAccessException e) {
        Log.e(TAG, "takePhoto: 相机访问异常",e);
    }
}

2、onResume

在onResume中,首先检查是否授予了相机权限,如果没有权限则走申请权限的步骤;如果有权限,则判断预览视图是否可用,如果可用则打开相机,如果不可用,添加预览视图监听器;传入预览视图监听器对象。

@Override
protected void onResume() {
    super.onResume();
    if(checkPermission()){
        if(previewView.isAvailable()){
            openCamera();
        }else {
            previewView.setSurfaceTextureListener(surfaceTextureListener);
        }
    }else {

        requestPermission();
    }
}

2.1、权限管理

2.1.1、检查权限

private boolean checkPermission(){
    return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
}

2.1.2、申请权限

private void requestPermission(){
    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA},PERMISSION_REQUEST_CODE);
}

申请权限要定义权限申请结果回调方法:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case PERMISSION_REQUEST_CODE:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "相机权限已授权", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(getApplicationContext(), "已拒绝相机权限", Toast.LENGTH_SHORT).show();
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                        != PackageManager.PERMISSION_GRANTED) {
                    showMessageOkCancel("你应该授权相机权限,否则无法使用相机",
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    requestPermission();
                                }
                            });
                }
            }
    }
}

如果用户没有及时授权,需要弹出对话框提示用户授权:

private void showMessageOkCancel(String message, DialogInterface.OnClickListener okListener){
    new AlertDialog.Builder(Camera.this)
            .setMessage(message)
            .setPositiveButton("ok",okListener)
            .setNegativeButton("Cancel",null)
            .create()
            .show();
}

2.2、检查预览是否可用

如果可用:

2.2.1、打开相机

@SuppressLint("MissingPermission")
private void openCamera(){
    try {
        displayOrientation = this.getWindowManager().getDefaultDisplay().getOrientation();
        if(displayOrientation == Surface.ROTATION_0 || displayOrientation == Surface.ROTATION_180){
            previewView.setAspectRation(photoSize.getHeight(),photoSize.getWidth());
        }else {
            previewView.setAspectRation(photoSize.getWidth(),photoSize.getHeight());
        }
        configureTransform(previewView.getWidth(),previewView.getHeight());
        mCmameraManager.openCamera(cameraId,cameraStateCallback,null);
    } catch (CameraAccessException e) {
        Log.e(TAG, "openCamera: 相机访问异常",e);
    }
}

配置预览尺寸:

private void configureTransform(int viewWidth,int viewHeight){
    if(null == previewView || null == photoSize){
        return;
    }
    int rotation = this.getWindowManager().getDefaultDisplay().getRotation();
    Matrix matrix = new Matrix();
    RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
    RectF bufferRect = new RectF(0, 0, photoSize.getHeight(), photoSize.getWidth());
    float centerX = viewRect.centerX();
    float centerY = viewRect.centerY();
    if(Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation){
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
        float scale = Math.max(
                (float) viewHeight / photoSize.getHeight(),
                (float) viewWidth / photoSize.getWidth());
        matrix.postScale(scale, scale, centerX, centerY);
        matrix.postRotate(90 * (rotation - 2), centerX, centerY);
    } else if (Surface.ROTATION_180 == rotation) {
        matrix.postRotate(180,centerX,centerY);
    }
    previewView.setTag(matrix);
}

定义相机回调:

CameraDevice.StateCallback cameraStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice camera) {
        Log.d(TAG, "onOpened: 相机已经启动");
        // 初始化ImageReader和Surface
        initReaderAndSurface();
        cameraDevice = camera;
        try {
            SurfaceTexture surfaceTexture = previewView.getSurfaceTexture();
            if(surfaceTexture == null){
                return;
            }
            surfaceTexture.setDefaultBufferSize(photoSize.getWidth(),photoSize.getHeight());
            previewSurface = new Surface(surfaceTexture);
            previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            previewRequestBuilder.addTarget(previewSurface);
            previewRequest = previewRequestBuilder.build();
            cameraDevice.createCaptureSession(Arrays.asList(previewSurface,photoSurface),sessionsStateCallback,null);
        } catch (CameraAccessException e) {
            Log.e(TAG, "onOpened: 相机访问异常",e);
        }
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice camera) {
        Log.d(TAG, "onDisconnected: 相机已断开连接");
    }

    @Override
    public void onError(@NonNull CameraDevice camera, int error) {
        Log.d(TAG, "onError: 相机打开出错");
    }
};

创建捕获请求时,需要定义会话状态回调:

CameraCaptureSession.StateCallback sessionsStateCallback = new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(@NonNull CameraCaptureSession session) {
        if(null == cameraDevice){
            return;
        }
        captureSession = session;
        try {
            captureSession.setRepeatingRequest(previewRequest,null,null);
        } catch (CameraAccessException e) {
            Log.e(TAG, "onConfigured: 相机访问异常",e);
        }
    }

    @Override
    public void onConfigureFailed(@NonNull CameraCaptureSession session) {

    }
};

如果不可用:

2.2.2、设置预览监听器

previewView.setSurfaceTextureListener(surfaceTextureListener);

定义预览监听回调

``` java TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { openCamera(); }

@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
    configureTransform(width,height);
}

@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
    return false;
}

@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {

}

}; ```