系列
- Camera2录制视频(音视频合成)及其YUV数据提取(一)- 相机预览
- Camera2录制视频(音视频合成)及其YUV数据提取(二)- YUV提取及图像转化
- Camera2录制视频(音视频合成)及其YUV数据提取(二)- MediaCodec与MediaMuxe联合使用
相机预览
简介
Camera2 是最新的 Android 相机框架 API,它取代了已弃用的相机框架库。 Camera2 为复杂的用例提供深入的控制,但需要您管理特定于设备的配置。
使用
步骤(一)获取管理器
获取Camera2 Manager管理类,初始化子线程
private CameraManager mCameraManager;
private Handler mBgHandler;
this.mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
HandlerThread hThread = new HandlerThread("camera2");
this.mBgHandler = new Handler(hThread.getLooper())
CameraManager 是一个负责查询和建立相机连接的系统服务
步骤(二)获取相机信息
通过CameraManager获取当前设备的camera list列表,并用cameraID获取相应相机信息CameraCharacteristics
private String mRearCameraID, mFrontCameraID, mCurrentCameraId;
private CameraCharacteristics mRearCameraCs, mFrontCameraCs;
private void getCameraListCameraCharacteristics() {
try {
//获取当前手机的所有摄像头id
String[] cameraList = this.mCameraManager.getCameraIdList();
for (String id : cameraList) {
//通过id获取对应相机信息结构
CameraCharacteristics ccs = this.mCameraManager.getCameraCharacteristics(id);
//获取前置/后置摄像头
Integer facing = ccs.get(CameraCharacteristics.LENS_FACING);
if (facing != null) {
if (facing == CameraCharacteristics.LENS_FACING_BACK) {
//后置
this.mRearCameraCs = ccs;
this.mRearCameraID = id;
//设置默认后置
this.mCurrentCameraId = this.mRearCameraID;
} else if (facing == CameraCharacteristics.LENS_FACING_FRONT) {
//前置
this.mFrontCameraCs = ccs;
this.mFrontCameraID = id;
}
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraCharacteristics 相机信息提供类,类内部有很多相机信息常量,其中有代表相机方向的 LENS_FACING;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等。
这里主要去拿到前后置摄像头并且设置当前cameraId(mCurrentCareamId)默认为后置
步骤(三)寻找最佳预览尺寸
寻找最佳预览尺寸BestPreviewSize
private Size previewSize;
//判断是前置or后置拿到对应的相机信息
CameraCharacteristics tmpCss = this.mCurrentCameraId.equals(this.mRearCameraID) ? this.mRearCameraCs : this.mFrontCameraCs;
//获取摄像头支持的所有输出格式和尺寸
StreamConfigurationMap map = tmpCss.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map != null) {
//根据SurfaceTexture获取输出大小尺寸列表
Size[] sizes = map.getOutputSizes(SurfaceTexture.class);
//寻找最佳尺寸
this.previewSize = findSuitablePreviewSize(Arrays.asList(sizes));
if (this.previewSizeCallback != null) {
//预览尺寸回调
this.previewSizeCallback.onBestSize(this.previewSize.getWidth(), this.previewSize.getHeight());
}
}
StreamConfigurationMap 这是所有输出格式的权威列表(和大小分别 * 适用于该格式)相机设备支持。 这还包含每个格式/大小的最小帧持续时间和停顿持续时间
步骤(四)初始化ImageReader类
设置输出格式回调主要设置格式是YUV_420_888即I420
private ImageReader mImageReader;
private ImageReader.OnImageAvailableListener imageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
//...后续主要代码
//close必须调用
image.close();
}
};
//初始化ImageReader传入预览尺寸宽高,设置相机输出图像格式I420
this.mImageReader = ImageReader.newInstance(this.previewSize.getWidth(), this.previewSize.getHeight(), ImageFormat.YUV_420_888, 1);
//设置相机数据回调监听
this.mImageReader.setOnImageAvailableListener(this.imageAvailableListener, this.mBgHandler);
Image对象的data被存储在Image类里面,构造参数maxImages控制了最多缓存几帧,新的images通过ImageReader的surface发送给ImageReader,类似一个队列,需要通过acquireLatestImage()或者acquireNextImage()方法取出Image。
需要在不使用完毕后将image.close()进行关闭,否则无法展示下一帧的图像以及数据会看到界面卡主现象
步骤(五)打开相机
相机的准备操作
private CameraDevice mCameraDevice;//相机设备驱动
private CameraDevice.StateCallback cameraStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
}
};
try {
//设置需要打开的摄像头id,设置相机状态回调
this.mCameraManager.openCamera(this.mCurrentCameraId, this.cameraStateCallback, this.mBgHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
CameraDevice.StateCallback 用于接收有关摄像机设备状态的更新的回调对象。必须提供此回调实例才能打开摄像机设备。
这些状态更新包括有关设备完成启动(允许调用CameraDevice.createCaptureSession(SessionConfiguration)),设备断开或关闭以及有关意外设备错误的通知。
步骤(六)相机预览
在此之前需要再xml中创建一个TextureView作为预览View进行输出图像,保证在onSurfaceTextureAvailable调用TextureView的draw方法时,如果还没有初始化SurfaceTexture。那么就会初始化它。初始化好时,就会回调这个接口。SurfaceTexture初始化好时,就表示可以接收外界的绘制指令。
private TextureView mAutoFitTextureView;
//相机请求构建者
private CaptureRequest.Builder mCaptureRequestBuilder;
this.mAutoFitTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
//ready
startPreview();
}
@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 startPreview() {
//获取TextureView中的SurfaceView
SurfaceTexture surfaceTexture = this.mAutoFitTextureView.getSurfaceTexture();
//设置预览数据中的缓冲区大小,按照最佳预览尺寸设置
surfaceTexture.setDefaultBufferSize(this.previewSize.getWidth(), this.previewSize.getHeight());
//创建一个新的surface用于添加target list
Surface surface = new Surface(surfaceTexture);
try {
//创建预览请求 TEMPLATE_PREVIEW
this.mCaptureRequestBuilder = this.mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
this.mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//add布局中的Surface 用于预览图像
this.mCaptureRequestBuilder.addTarget(surface);
//将回调预览数据data给ImageReader的Surface对象,此时就会回调设置的ImageReader监听器
this.mCaptureRequestBuilder.addTarget(mImageReader.getSurface());
//相机设备驱动创建session 第一个参数传入设置的target<Surface>list 上面加了下面list也要加入,第二个参数session状态回调,第三handler线程
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), this.cameraSessionStateCallback, this.mBgHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private CameraCaptureSession mCameraCaptureSession;
private CameraCaptureSession.StateCallback cameraSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
try {
//利用session#setRepeatingRequest发送无限重复的指令(预览类型指令),第一个参数即传 请求构建者.build()该对象上面已经设置为预览(TEMPLATE_PREVIEW)类型
mCameraCaptureSession.setRepeatingRequest(mCaptureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
}, mBgHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
};
CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息build,内部包括了本次 Capture 的参数配置和接收图像数据的 Surface,CaptureRequest 可配置的信息,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等,绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。
CameraCaptureSession 是配置了目标 Surface 的 Pipeline 实例,在使用相机功能之前必须先创建 CameraCaptureSession 实例。一个 CameraDevice 一次只能开启一个 CameraCaptureSession,大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等等。
当设置完setRepeatingRequest的时候再界面中就已经能看到相机的预览效果,并且也在不断的回调ImageReader监听
结尾
由于本内容动较多,加上camer2步骤还是比较繁琐的,下一篇会说一下再
YUV_420_888的预览数据输出格式下如何提取YUV420(I420,NV21及NV12都属于YUV420范畴)的数据进行图像操作