从5.0开始(API Level 21),可以完全控制安卓设备相机的新api Camera2(android.hardware.Camera2)被引入了进来。在以前的Camera api(android.hardware.Camera)中,对相机的手动控制需要更改系统才能实现,而且api看着方便,但是不好管理。不过老的Camera API在5.0上已经过时(依然兼容),如今Android推荐使用Camera2采集视频,借着写这篇记录的过程,熟悉和理解Camera2流程。
YUV
一种颜色编码的方法,在旧Camera API 常用的是NV21和YV12,可以转成RGB编码。
//获取Camera2 支持的颜色编码
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
map.getOutputFormats();
CameraManager
Camera2中负责管理、查询摄像头信息、打开可用的摄像头,这也是Camera2 API2与api1的区别之一:
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
CameraDevice
具体的摄像头,提供一组属性信息,描述硬件设备以及设备的可用设置和参数。
manager.openCamera(mCameraId, stateCallback, mHandler);//打开Camera
/**摄像头状态的监听*/
private CameraDevice.StateCallback stateCallback = new CameraDevice. StateCallback()
{
// 摄像头被打开时触发该方法
@Override
public void onOpened(CameraDevice cameraDevice){
CameraDemoActivity.this.cameraDevice = cameraDevice;
// 开始预览
takePreview();
}
// 摄像头断开连接时触发该方法
@Override
public void onDisconnected(CameraDevice cameraDevice)
{
CameraDemoActivity.this.cameraDevice.close();
CameraDemoActivity.this.cameraDevice = null;
}
// 打开摄像头出现错误时触发该方法
@Override
public void onError(CameraDevice cameraDevice, int error)
{
cameraDevice.close();
}
};
CaptureRequest
一次捕获请求,通过CaptureRequest.Builder的build()创建,其实请求参数也是通过Buider来设置:
CaptureRequest.Builder常用的方法:
//创建预览请求
mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 设置自动对焦模式
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 3);
//设置Surface作为预览数据的显示界面
mCaptureRequestBuilder.addTarget(mSurface);
此处用一段设置闪光灯的代码进行说明:
private void openFlash(){
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
//设置反复捕获数据的请求,这样预览界面就会一直有数据显示
try {
mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraCaptureSession
捕获的会话Session,预览、拍照,都由该它进行控制的。
//创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
cameraDevice.createCaptureSession(Arrays.asList(mSurface,imageReader.getSurface()),new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
Log.d("onConfigured","onConfigured");
try {
//开始预览
mCaptureRequest = mCaptureRequestBuilder.build();
mPreviewSession = session;
//设置反复捕获数据的请求,这样预览界面就会一直有数据显示
mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
Log.d("onConfigureFailed","onConfigureFailed");
}
}, null);
在写代码的时候发现上面几个callback弄不清楚
发现还有一个Handle一直跟着走,究竟是什么东西呢?
//很多过程都变成了异步的了,所以这里需要一个子线程的looper
private void initLooper() {
mThreadHandler = new HandlerThread("CAMERA2");
mThreadHandler.start();
mHandler = new Handler(mThreadHandler.getLooper());
}
TextureView预览
SurfaceTexture mSurfaceTexture = surfaceView.getSurfaceTexture();
//设置TextureView的缓冲区大小
mSurfaceTexture.setDefaultBufferSize(1920, 1080);
//获取Surface显示预览数据
Surface mSurface = new Surface(mSurfaceTexture);
SurfaceView预览
Surface surface = mSurfaceHolder.getSurface();
CameraCharacteristics
直接上代码:
/**设置摄像头的参数*/
private void setCameraCharacteristics(CameraManager manager)
{
try
{
// 获取指定摄像头的特性
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(mCameraId);
// 获取摄像头支持的配置属性
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// 获取摄像头支持的最大尺寸
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)),new CompareSizesByArea());
List<Size> sizeList = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));
for (Size s:sizeList) {
Log.d(" Camera "," picturesize w " + s.getWidth() + " picturesize h = " + s.getHeight());
}
// 创建一个ImageReader对象,用于获取摄像头的图像数据
imageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.JPEG, 2);
//设置获取图片的监听
imageReader.setOnImageAvailableListener(imageAvailableListener,mHandler);
// 获取最佳的预览尺寸
previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), mWidth, mHeight, largest);
Log.d(" Camera"," previewSize w " + previewSize.getWidth() + " previewSize h = " + previewSize.getHeight() + " mWidth = " + mWidth + " mHeight = " +mHeight);
}
catch (CameraAccessException e)
{
e.printStackTrace();
}
catch (NullPointerException e)
{
}
}
private static Size chooseOptimalSize(Size[] choices
, int width, int height, Size aspectRatio)
{
// 收集摄像头支持的大过预览Surface的分辨率
List<Size> bigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices)
{
if (option.getHeight() == option.getWidth() * h / w &&
option.getWidth() >= width && option.getHeight() >= height)
{
bigEnough.add(option);
}
}
// 如果找到多个预览尺寸,获取其中面积最小的
if (bigEnough.size() > 0)
{
return Collections.min(bigEnough, new CompareSizesByArea());
}
else
{
//没有合适的预览尺寸
return choices[0];
}
}
// 为Size定义一个比较器Comparator
public static class CompareSizesByArea implements Comparator<Size>
{
@Override
public int compare(Size lhs, Size rhs)
{
// 强转为long保证不会发生溢出
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}