Camera2的简单使用

本文介绍了一种自适应布局的TextureView实现方案,通过设定宽高比使视图能够自动调整大小以匹配不同屏幕尺寸。此外,还详细阐述了如何在Android应用中使用TextureView进行摄像头预览、拍照及摄像头切换等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 布局

AutoFitTextureView

public class AutoFitTextureView extends TextureView {

    private int mRatioWidth = 0;
    private int mRatioHeight = 0;

    public AutoFitTextureView(Context context) {
        this(context, null);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
     * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
     * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
     *
     * @param width  Relative horizontal size
     * @param height Relative vertical size
     */
    public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        if (mRatioWidth == width && mRatioHeight == height) {
            return;
        }
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (0 == mRatioWidth || 0 == mRatioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }
}

二. 基本设置

1. 请求权限(onResume中处理)

避坑:
a. 请求到权限之后初始化一次即可, 所以加了个标志位

private void initCamera() {
    if (mFlag) {
        return;
    }
    mFlag = true;
    mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}

b. 重赋权限之后返回界面没刷新依然黑屏
TextureView.SurfaceTextureListener的监听方法中处理
添加全局标志位mFlagCameraAvailable;
onSurfaceTextureAvailable

onSurfaceTextureAvailable=true

onSurfaceTextureUpdated

if (!onSurfaceTextureAvailable) {
    recreate();
    onSurfaceTextureAvailable= true;
}

解释: 在设置页面重新赋予权限之后,监听mTextureView.setSurfaceTextureListener(mSurfaceTextureListener)依然会执行, 但是onSurfaceTextureAvailable不执行, 而onSurfaceTextureUpdated会执行一次, 所以在该方法中重建该页面

2. 设置TextureView的监听

mTextureView.setSurfaceTextureListener(mSurfaceTextureListener)
onSurfaceTextureAvailable方法中开启相机:

  1. 开启HandlerThread
HandlerThread thread = new HandlerThread("TextureView.Available");
thread.start();
  1. 创建全局Handler对象
mHandler = new Handler(thread.getLooper());

接下来调用的api都需要该mHandler对象

  1. 获取CameraManager对象
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  1. 获取摄像头数据
mCharacteristics = manager.getCameraCharacteristics(mCameraId);

此处mCameraId为常数, 可取CameraCharacteristics.LENS_FACING_FRONT
CameraCharacteristics.LENS_FACING_BACK 即前置和后置摄像头, 此处需转为String类型;

  1. 获取摄像头参数:
StreamConfigurationMap map =  mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  1. 获取照片分辨率列表
List<Size> sizes = map.getOutputSizes(ImageFormat.JPEG);

获取其中最大的分辨率(Collections.max(sizes, new CompareSizeByArea())), 用于获取ImageReader对象

  1. 获取预览尺寸
mPreViewSize = map.getOutputSizes(SurfaceTexture.class)[0];
  1. 获取ImageReader对象
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 5); //5表示同时访问的最大照片数量,越小越省内存
  1. 设置监听图片的监听
mImageReader.setOnImageAvailableListener(onImageAvailableListener, mHandler);
  1. 开启相机
manager.openCamera(mCameraId, mCameraOpenCallback, mHandler);

1-10即为下面的openCamera()方法

CameraCharacteristics相关参数

//获取摄像头方向
characteristics.get(CameraCharacteristics.LENS_FACING)

3. 监听之mCameraOpenCallback

CameraDevice.StateCallback

onOpened方法

  1. 取到CameraDevice作为全局相机设备对象, 在界面关闭的时候需要对其关闭
  2. 创建全局CaptureRequest.Builder对象, 并创建会话
mPreViewBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
surfaceTexture.setDefaultBufferSize(mPreViewSize.getWidth(), mPreViewSize.getHeight());
Surface surface = new Surface(surfaceTexture);
mPreViewBuilder.addTarget(surface);
List<Surface> surfaces = Arrays.asList(surface, mImageReader.getSurface());
camera.createCaptureSession(surfaces, mSessionStateCallBack, mHandler);

4. 监听之mSessionStateCallBack

CameraCaptureSession.StateCallback

  1. 取到CameraCaptureSession作为全局会话对象, 拍照时候需要用到, 在界面关闭的时候需要对其关闭
  2. 给会话设置
session.setRepeatingRequest(mPreViewBuilder.build(), null, mHandler);

5. 监听之mOnImageAvailableListener

ImageReader.OnImageAvailableListener
onImageAvailable()

//1. 获取到拍摄的照片
Image image = imageReader.acquireNextImage()
//2. 操作保存
mHandler.post(new Runnable() {
    @Override
    public void run() {
        saveImage();
    }
});
private void saveImage() {
	String path = getExternalCacheDir().getAbsolutePath().concat(File.separator).concat("temp_image").concat(".jpg");
	FileOutputStream outputStream = null;
	try {
	    outputStream = new FileOutputStream(path);  // todo: 2020/11/4 [V1.5.4] file
	    ByteBuffer buffer = reader.getPlanes()[0].getBuffer();
	    byte[] buff = new byte[buffer.remaining()];
	    buffer.get(buff);
	    outputStream.write(buff);
	    XLogUtil.d("拍摄保存图片完成", path);
	} catch (IOException e) {
	    e.printStackTrace();
	} finally {
	    if (reader != null) {
	        reader.close();
	    }
	    if (outputStream != null) {
	        try {
	            outputStream.close();
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	    }
	}
}

三. 拍照

CaptureRequest.Builder builder = mCameraSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(mImageReader.getSurface());
//自动对焦
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
//照片方向
builder.set(CaptureRequest.JPEG_ORIENTATION, mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION));
//拍照
mCameraSession.capture(builder.build(), null, mHandler);

四. 切换摄像头

if (mCameraId.equals(CameraCharacteristics.LENS_FACING_FRONT + "")) {
    mCameraId = CameraCharacteristics.LENS_FACING_BACK + "";
    closeCamera();
    reopenCamera();

} else if (mCameraId.equals(CameraCharacteristics.LENS_FACING_BACK + "")) {
    mCameraId = CameraCharacteristics.LENS_FACING_FRONT + "";
    closeCamera();
    reopenCamera();
}
private void closeCamera() {
    if (mCameraDevice != null) {
        mCameraDevice.close();
        mCameraDevice = null;
    }
    if (mCameraSession != null) {
        mCameraSession.close();
        mCameraSession = null;
    }
}
private void reopenCamera() {
    if (mTextureView.isAvailable()) {
        openCamera();
    } else {
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    }
}
//即重新执行onSurfaceTextureAvailable内的方法
private void openCamera() {
    CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        mCharacteristics = manager.getCameraCharacteristics(mCameraId);
        StreamConfigurationMap map = mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        List<Size> sizes = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));
        Size largestImageSize = Collections.max(sizes, new CompareSizeByArea());
        Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
        if (outputSizes != null && outputSizes.length > 0) {
            mPreViewSize = outputSizes[0];
        }
        mImageReader = ImageReader.newInstance(largestImageSize.getWidth(), largestImageSize.getHeight(), ImageFormat.JPEG, 5);
        mImageReader.setOnImageAvailableListener(onImageAvailableListener, mHandler);
        manager.openCamera(mCameraId, mCameraOpenCallback, mHandler);
        configureTransform(mPreViewSize.getWidth(), mPreViewSize.getHeight());
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值