简介:
Android 5.0开始出现了新的相机Camera 2 API,用来替代以前的camera api。
Camera2 API不仅提高了android系统的拍照性能,还支持RAW照片输出,还可以设置相机的对焦模式,曝光模式,快门等等。
Camera2 中主要的API类
CameraManager类 : 摄像头管理类,用于检测、打开系统摄像头,通过getCameraCharacteristics(cameraId)
可以获取摄像头特征。
CameraCharacteristics类:相机特性类,例如,是否支持自动调焦,是否支持zoom,是否支持闪光灯一系列特征。
CameraDevice类: 相机设备,类似早期的camera类。
CameraCaptureSession类:用于创建预览、拍照的Session类。通过它的setRepeatingRequest()
方法控制预览界面 , 通过它的capture()
方法控制拍照动作或者录像动作。
CameraRequest类:一次捕获的请求,可以设置一些列的参数,用于控制预览和拍照参数,例如:对焦模式,曝光模式,zoom参数等等。
接下来,进一步介绍,Camera2 API中的各种常见类和抽象类。
CameraManager类
CameraCharacteristics cameraCharacteristics =manager.getCameraCharacteristics(cameraId);
通过以上代码可以获取摄像头的特征对象,例如: 前后摄像头,分辨率等。
CameraCharacteristics类
相机特性类
CameraCharacteristics是一个包含相机参数的对象,可以通过一些key获取对应的values.
以下几种常用的参数:
LENS_FACING:获取摄像头方向。LENS_FACING_FRONT是前摄像头,LENS_FACING_BACK是后摄像头。
SENSOR_ORIENTATION:获取摄像头拍照的方向。
FLASH_INFO_AVAILABLE:获取是否支持闪光灯。
SCALER_AVAILABLE_MAX_DIGITAL_ZOOM:获取最大的数字调焦值,也就是zoom最大值。
LENS_INFO_MINIMUM_FOCUS_DISTANCE:获取最小的调焦距离,某些手机上获取到的该values为null或者0.0。前摄像头大部分有固定焦距,无法调节。
INFO_SUPPORTED_HARDWARE_LEVEL:获取摄像头支持某些特性的程度。
以下手机中支持的若干种程度:
INFO_SUPPORTED_HARDWARE_LEVEL_FULL:全方位的硬件支持,允许手动控制全高清的摄像、支持连拍模式以及其他新特性。
INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:有限支持,这个需要单独查询。
INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:所有设备都会支持,也就是和过时的Camera API支持的特性是一致的。
CameraDevice类
CameraDevice的reateCaptureRequest(int templateType)
方法创建CaptureRequest.Builder。
templateType参数有以下几种:
TEMPLATE_PREVIEW :预览
TEMPLATE_RECORD:拍摄视频
TEMPLATE_STILL_CAPTURE:拍照
TEMPLATE_VIDEO_SNAPSHOT:创建视视频录制时截屏的请求
TEMPLATE_ZERO_SHUTTER_LAG:创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量。
TEMPLATE_MANUAL:创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)。
CameraDevice.StateCallback抽象类
该抽象类用于CemeraDevice相机设备状态的回调。
/**
* 当相机设备的状态发生改变的时候,将会回调。
*/
protected final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
/**
* 当相机打开的时候,调用
* @param cameraDevice
*/
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
startPreView();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
mCameraDevice = null;
}
/**
* 发生异常的时候调用
*
* 这里释放资源,然后关闭界面
* @param cameraDevice
* @param error
*/
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
mCameraDevice = null;
}
/**
*当相机被关闭的时候
*/
@Override
public void onClosed(@NonNull CameraDevice camera) {
super.onClosed(camera);
}
};
CameraCaptureSession.StateCallback抽象类
该抽象类用于Session过程中状态的回调。
public static abstract class StateCallback {
//摄像头完成配置,可以处理Capture请求了。
public abstract void onConfigured(@NonNull CameraCaptureSession session);
//摄像头配置失败
public abstract void onConfigureFailed(@NonNull CameraCaptureSession session);
//摄像头处于就绪状态,当前没有请求需要处理
public void onReady(@NonNull CameraCaptureSession session) {}
//摄像头正在处理请求
public void onActive(@NonNull CameraCaptureSession session) {}
//请求队列中为空,准备着接受下一个请求。
public void onCaptureQueueEmpty(@NonNull CameraCaptureSession session) {}
//会话被关闭
public void onClosed(@NonNull CameraCaptureSession session) {}
//Surface准备就绪
public void onSurfacePrepared(@NonNull CameraCaptureSession session,@NonNull Surface surface) {}
}
接下来,是介绍拍照和录像流程步骤。
使用流程:
1. 打开指定的方向的相机
最先获取CameraManager对象,通过该对象的getCameraIdList()
获取到一些列的摄像头参数。
通过循环匹配,获取到指定方向的摄像头,例如后摄像头等。
CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
//获取到可用的相机
for (String cameraId : manager.getCameraIdList()) {
//获取到每个相机的参数对象,包含前后摄像头,分辨率等
CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(cameraId);
//摄像头的方向
Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
if(facing==null){
continue;
}
//匹配方向,指定打开后摄像头
if(facing!=CameraCharacteristics.LENS_FACING_BACK){
continue;
}
//打开指定的摄像头
manager.openCamera(mCameraId, stateCallback, workThreadManager.getBackgroundHandler());
return;
}
当然,实际开发中,还需要获取相机支持的特性(闪光灯,zoom调焦,手动调焦等),和设置摄像头的参数(例如:预览的Size)。
2. 创建预览的界面:
创建 CameraDevice.StateCallback 对象,且开启一个相机。当相机开启后,将出现相机预览界面。
CameraDevice.StateCallback 对象传入CameraManager中openCamera(mCameraId, stateCallback, workThreadManager.getBackgroundHandler())
的第二个参数,用于监听摄像头的状态。
/**
* 相机设备
*/
protected CameraDevice mCameraDevice;
/**
* 当相机设备的状态发生改变的时候,将会回调。
*/
protected final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
/**
* 当相机打开的时候,调用
* @param cameraDevice
*/
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
// 省略该状态接口的部分方法
...............
};
/**
* 预览请求的Builder
*/
private CaptureRequest.Builder mPreviewRequestBuilder;
/**
* 相机开始预览,创建一个CameraCaptureSession对象
*/
private void createCameraPreviewSession() {
// 将CaptureRequest的构建器与Surface对象绑定在一起
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 为相机预览,创建一个CameraCaptureSession对象
mCameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), stateCallback, null);
}
创建完预览的界面后,接下来需要开始刷新。
3. 在预览界面过程中,需要间隔刷新界面
相机预览使用TextureView来实现。创建一个CameraCaptureSession ,通过一个用于预览界面的CaptureRequest,间隔复用给CameraCaptureSession。
private CameraCaptureSession mCaptureSession;
CameraCaptureSession.StateCallback stateCallback=new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
//当cameraCaptureSession已经准备完成,开始显示预览界面
mCaptureSession = cameraCaptureSession;
setCameraCaptureSession();
}
//省略该接口的部分方法
.......
}
/**
* 设置CameraCaptureSession的特征:
*
* 自动对焦,闪光灯
*/
private void setCameraCaptureSession() {
//设置预览界面的特征,通过mPreviewRequestBuilder.set()方法,例如,闪光灯,zoom调焦等
..........
//为CameraCaptureSession设置间隔的CaptureRequest,用间隔刷新预览界面。
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, workThreadManager.getBackgroundHandler());
}
只要未开始拍照动作或者录像动作,该复用的CaptureRequest会重复的刷新预览界面。
接下来,等待用户点击拍照按钮或者录像按钮,进行拍照动作,或者录像动作。
4. 拍照动作:
首先锁住焦点,通过在相机预览界面个性CaptureRequest。然后,以类似方式,需要运行一个预捕获序列。接下来,可已经准备好捕捉图片。创建一个新的CaptureRequest,且拍照。
/**
* 拍照一个静态的图片
* ,当在CaptureCallback监听器响应的时候调用该方法。
*
* 当数字调焦缩放的时候,在写入图片数中也要设置。
*/
private void captureStillPicture() {
try {
// 创建一个拍照的CaptureRequest.Builder
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(imageReader.getSurface());
//设置一系列的拍照参数,这里省略
...........
//先停止以前的预览状态
mCaptureSession.stopRepeating();
mCaptureSession.abortCaptures();
//执行拍照动作
mCaptureSession.capture(captureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
拍照界面产生的数据只是在手机内存中,图片是一个磁盘文件,还需要一个将拍照产生数据写入文件中的操作类ImageReader。
先是创建ImageReader对象,和设置监听器等一些列参数。
/**
* 处理静态图片的输出
*/
private ImageReader imageReader;
//对于静态图片,使用可用的最大值来拍摄。
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizeByArea());
//设置ImageReader,将大小,图片格式
imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, /*maxImages*/2);
imageReader.setOnImageAvailableListener(onImageAvailableListener, workThreadManager.getBackgroundHandler());
接下来,将ImageReader的surface配置到captureBuilder对象中captureBuilder.addTarget(imageReader.getSurface());
最后,当拍照完成后,会在该监听状态中回调:
/**
* ImageReader的回调监听器
*
* onImageAvailable被调用的时候,已经拍照完,准备保存的操作
* 通常写入磁盘文件中。
*/
protected final ImageReader.OnImageAvailableListener onImageAvailableListener = (ImageReader reader)
-> writePictureData(reader.acquireNextImage());
public void writePictureData(Image image) {
if (camera2ResultCallBack != null) {
camera2ResultCallBack.callBack(ObservableBuilder.createWriteCaptureImage(appContext, image));
}
}
/**
* 将JPEG图片的数据,写入磁盘中
*
* @param context
* @param mImage
* @return
*/
public static Observable createWriteCaptureImage(final Context context, final Image mImage) {
Observable observable = Observable.create(subscriber -> {
File file = FileUtils.createPictureDiskFile(context, FileUtils.createBitmapFileName());
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
FileOutputStream output = null;
try {
output = new FileOutputStream(file);
output.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImage.close();
if (null != output) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
subscriber.onNext(file.getAbsolutePath());
});
return observable;
}
这里采用RxJava+RxAndroid异步通讯,避免太多回调接口。
5. 录像动作
录像是长时间的动作,录像过程中需要重复性的刷新录制界面。其余的步骤和拍照动作基本类似。
/**
* 开始视频录制。
*/
private void startRecordingVideo() {
try {
//创建录制的session会话中的请求
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
//设置录制参数,这里省略
.........
// Start a capture session
// Once the session starts, we can update the UI and start recording
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
mPreviewSession = cameraCaptureSession;
Log.i(TAG, " startRecordingVideo 正式开始录制 ");
updatePreview();
}
//该接口的方法,部分省略
.............
}, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException | IOException e) {
e.printStackTrace();
}
}
//录制过程中,不断刷新录制界面
private void updatePreview() {
try {
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
和拍照类似,将视频数据写入磁盘文件中,也是需要一个操作类 MediaRecorder来实现的。
先是创建该操作类对象,设置一些列参数:
/**
* MediaRecorder
*/
private MediaRecorder mMediaRecorder;
/**
* 设置媒体录制器的配置参数
*
* 音频,视频格式,文件路径,频率,编码格式等等
*
* @throws IOException
*/
private void setUpMediaRecorder() throws IOException {
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mNextVideoAbsolutePath = FileUtils.createVideoDiskFile(appContext, FileUtils.createVideoFileName()).getAbsolutePath();
mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
mMediaRecorder.setVideoEncodingBitRate(10000000);
//每秒30帧
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
switch (mSensorOrientation) {
case SENSOR_ORIENTATION_DEFAULT_DEGREES:
mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
break;
case SENSOR_ORIENTATION_INVERSE_DEGREES:
mMediaRecorder.setOrientationHint(ORIENTATIONS.get(rotation));
break;
default:
break;
}
mMediaRecorder.prepare();
}
间隔性的随着视频录制而输出数据到文件中。
// 为 MediaRecorder设置Surface
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewBuilder.addTarget(recorderSurface);
最后,当录制视频结束后,停止输出:
// 停止录制
mMediaRecorder.stop();
mMediaRecorder.reset();
6. 恢复到预览界面:
完成一些列拍照或录像动作后,重新恢复到预览界面。
/**
* 完成一些列拍照或录像动作后,释放焦点。
*/
private void unlockFocus() {
try {
//向session重新发送,预览的间隔性请求,出现预览界面。
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
当然,还有关闭相机操作,和与Activity生命周期绑定的操作,这里不再做介绍了。
以上代码来源于,Camera2App项目。
下一篇,开始介绍,如何开发一个真正的相机程序。
资源参考:
官方关于camera2 博客介绍:
https://medium.com/google-developers/detecting-camera-features-with-camera2-61675bb7d1bf
极客学院关于Camera2的介绍:
http://wiki.jikexueyuan.com/project/android-actual-combat-skills/android-hardware-camera2-operating-guide.html
Google的CameraView库
android-Camera2Basic
android-Camera2Raw
官方的android-Camera2Video