参考:
Camera v2文档1:https://www.jianshu.com/p/73fed068a795
Camera v2文档2:https://blog.csdn.net/qq_38106356/article/details/77996319
github参考代码:https://github.com/googlesamples/android-Camera2Basic
参数调节关键字翻译集合,常用关键字解析文档:
https://blog.csdn.net/qq_29333911/article/details/79400617
https://blog.csdn.net/sadamoo/article/details/50370702
官方文档:https://developer.android.com/reference/android/hardware/camera2/CaptureRequest.html
一、前言:
Android 5.0(21)之后android.hardware.Camera就被废弃了,取而代之的是全新的android.hardware.Camera2。
Android 5.0对拍照API进行了全新的设计,新增了全新设计的Camera v2 API,这些API不仅大幅提高了Android系统拍照的功能,还能支持RAW照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。
常识:
------------相机参数:快门
快门就是相当于是一个门,在拍照的时候光通过这个门时间。快门速度越快,进光越少,那么拍照的速度就会越快。在运动中我们的快门速度通常会设置的快一点的,快门快的可以将运动的人拍的清晰,而且可以拍到运动中我们人眼看不到的动作
------------相机参数:光圈
光圈值越小,光圈越大,相当于相机的门较大,进入的光较多。在光弱的时候我通常是加大光圈的,加大光圈就是在手机上把光圈值调小就可了。光圈越大,焦点之外的清晰范围越小,焦点之外的模糊程度就越大。这里所说的模糊是虚焦模糊,也就是没对上焦的模糊。
------------相机参数 :感光度
感光度大部分都是自动的,相机的感光度顾名思义就是相片的亮度。在亮度低的地方我们通常要调高感光度的,同时也要减少曝光时间的,感光度高的时候需要更长的时间来提高相片的亮度的,一般在黑暗中提高感光度的同时一般都要提高曝光时间,曝光时间是通过快门调的,这时候就需要将快门时间调大点就可以。
感光度达到一定数值后(根据相机性能不同而不同),其值越高,画面中的噪点就越多,物体细节就越粗糙,画面的颗粒感就越重。进而会直接影响到画面细节表现,并且产生色彩偏差的可能性就越大。
二、Camera v2主要类说明
1、CameraManager:摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。
2、CameraCharacteristics:硬件摄像头特性。该对象通过CameraManager来获取,用于描述特定摄像头硬件所支持的各种特性。调用CameraManager的getCameraCharacteristics(cameraID)方法即可获取指定摄像头的相关特性。
3、CameraDevice:当前打开的摄像头类。该类的功能类似于早期的Camera类
4、CameraCaptureSession:这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest();控制拍照的方法为capture()。
5、CameraMetadata:控制相机和带有相机参数的基础类,它的子类是: CameraCharacteristics,CaptureRequest,CaptureResult。
三、使用
1、权限,6.0以后添加动态权限的获取:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
2、获取相机硬件属性
CameraManager manager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
try {
//获取可用摄像头列表
for (String cameraId : manager.getCameraIdList()) {
//获取相机的相关参数
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
// 不使用前置摄像头。
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// 检查闪光灯是否支持。
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
//CameraID
mCameraId = cameraId;
Log.e(TAG," 相机可用 ");
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
//不支持Camera2API
}
}
3、打开指定相机(mCameraId)
1)打开相机
CameraManager manager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
try {
//打开相机预览
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
2)回调,获取当前相机设备的java对象mCameraDevice。
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
//创建CameraPreviewSession
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
mCameraDevice = null;
}
};
3)mBackgroundHandler定义: onResume中调用startBackgroundThread;onPause中调用stopBackgroundThread
/**
* Starts a background thread and its {@link Handler}.
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
/**
* Stops the background thread and its {@link Handler}.
*/
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
4、关联surface和相机mCameraDevice:创建预览;设置当前相机预览时的参数;发送请求,无限次获取图像
/**
* 为相机预览创建新的CameraCaptureSession
*/
private void createCameraPreviewSession() {
try {
//CaptureRequest用的是builder模式,创建一个针对预览的CaptureRequest,设置其预览的surface
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
//创建一个CameraCaptureSession来进行相机预览。在安卓端和相机硬件端建立通道,进行信息的交换
mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// 相机已经关闭
if (null == mCameraDevice) {
return;
}
// 会话准备好后,我们开始显示预览
mCaptureSession = cameraCaptureSession;
try {
// 自动对焦应
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 闪光灯
setAutoFlash(mPreviewRequestBuilder);
// 开启相机预览并添加事件
mPreviewRequest = mPreviewRequestBuilder.build();
//发送请求,无限次的重复获取图像
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
Log.e(TAG," 开启相机预览并添加事件");
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e(TAG," onConfigureFailed 开启预览失败");
}
}, null);
} catch (CameraAccessException e) {
Log.e(TAG," CameraAccessException 开启预览失败");
e.printStackTrace();
}
}
private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
private void process(CaptureResult result) {
//拍摄完全完成并且成功,拍摄图像数据可用时回调
}
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
//拍摄进行中,但拍摄图像数据部分可用时回调
process(partialResult);
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
//拍摄失败时回调
process(result);
}
};
如上,分三部:
1)创建CameraCaptureSession会话对象:全新的API引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata。
createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) :
第一参数就是我们需要输出到的Surface列表,这里我们可以输出到一个SurfaceView中或者TextureView中。
第二参数是对创建过程的一个回调方法,当onConfigured回调的时候说明CameraCaptureSession创建成功了。
2)创建CaptureRequest请求,并设置当前相机预览时的参数:
createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 这里我们发送的TEMPLATE_PREVIEW也就是告诉相机我们只需要预览。其他参数:
TEMPLATE_RECORD 创建适合录像的请求。
TEMPLATE_PREVIEW 创建一个适合于相机预览窗口的请求。
TEMPLATE_STILL_CAPTURE 创建适用于静态图像捕获的请求
TEMPLATE_VIDEO_SNAPSHOT 在录制视频时创建适合静态图像捕获的请求。
3)上面两个创建完毕后,调用mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler); 发情求开启预览,并无限次的重复请求获取图像。mCaptureCallback作为回调不断输出数据
5、拍照
1)对焦
/**
* 将焦点锁定为静态图像捕获的第一步。(对焦)
*/
private void lockFocus() {
try {
// 相机对焦
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START);
// 修改状态
mState = STATE_WAITING_LOCK;
//CameraCaptureSession发送对焦请求
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
2)对对焦是否成功进行监听,在mCaptureCallback中对回调进行处理
/**
* 处理与JPEG捕获有关的事件
*/
private CameraCaptureSession.CaptureCallback mCaptureCallback
= new CameraCaptureSession.CaptureCallback() {
//处理
private void process(CaptureResult result) {
switch (mState) {
case STATE_PREVIEW: {
//预览状态
break;
}
case STATE_WAITING_LOCK: {
//等待对焦
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (afState == null) {
captureStillPicture();
} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
mState = STATE_PICTURE_TAKEN;
//对焦完成
captureStillPicture();
} else {
runPrecaptureSequence();
}
}
break;
}
}
}
}
3)向CameraCaptureSession发送请求拍照:
创建拍照请求mCaptureRequest对象;设置参数;发请求拍照:
/**
*
* 拍摄静态图片。
*/
private void captureStillPicture() {
try {
if ( null == mCameraDevice) {
return;
}
// 这是用来拍摄照片的CaptureRequest.Builder。
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
// 使用相同的AE和AF模式作为预览。
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
setAutoFlash(captureBuilder);
// 方向
int rotation = this.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
CameraCaptureSession.CaptureCallback CaptureCallback
= new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
showToast("Saved: " + mFile);
Log.d(TAG, mFile.toString());
unlockFocus();
}
};
//停止连续取景
mCaptureSession.stopRepeating();
//捕获图片
mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
4)拍照后,拿到数据进行保存:
------------这里要说回在创建CameraCaptureSession时参数不是有一个输出的Surface列表么,在列表中添加一个ImageReader的Surface用户获取图片数据(ImageReader是一个可以让我们对绘制到surface的图像进行直接操作的类),
即:
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback()...)
------------在ImageReader中对图片获取就行监听:
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//当图片可得到的时候获取图片并保存,reader.acquireNextImage()我们就可以拿到当前的图片数据了
mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
reader.acquireNextImage().close();
}
};
注意:一定要调用reader.acquireLatestImage()和close()方法,否则画面就会卡住
5)拍完后我们需要解锁焦点让相机回到预览状态
/**
* 解锁焦点
*/
private void unlockFocus() {
try {
// 重置自动对焦
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
setAutoFlash(mPreviewRequestBuilder);
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
mBackgroundHandler);
// 将相机恢复正常的预览状态。
mState = STATE_PREVIEW;
// 打开连续取景模式
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
6、预览图片实时输出
mPreviewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
mPreviewRequestBuilder.addTarget(mImageReader.getSurface());//添加mImageReader.getSurface(),这样会在mOnImageAvailableListener监听中会实时收到预览数据
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback()...)
7、注意点
Camera2 API配合SurfaceView暂时没有找到设置输出分辨率的接口。导致mOnImageAvailableListener监听中获取的Image对象,data数据的分辨率和image的宽高(mImageReader对象创建时设置)不一样。
Camera2 API根据google官方API配合TextureView进行预览
四、其他
1、通过设置ANDROID_CONTROL_MODE控制3A。可以选择没有3A(ANDROID_CONTROL_MODE_OFF),自动模式(ANDROID_CONTROL_MODE_AUTO)和场景模式(ANDROID_CONTROL_USE_SCENE_MODE)。
· 在OFF模式下,自动聚焦(AF),自动曝光(AE)和自动白平衡(AEB)模式都被关闭。3A事例不会重置捕获控制中的任何设置。
· 在AUTO模式下,AF,AE和AWB模式运行各自的独立算法,它们有自己的模式,状态和触发器元数据实体,如下段描述。
· 在USE_SCENE_MODE模式下,ANDROID_CONTROL_SCENE_MODE的值决定3A事例的行为。在除了FACE_PRIORITY的SCENE_MODE中,HAL层必须将ANDROID_CONTROL_AE/AWB/AF_MODE的值重置为更适合被选择的SCENE_MODE的模式。例如,HAL层喜欢在SCENE_MODE_NIGHT场景中使用AF的CONTINUOUS_FOCUS模式。当这些场景模式被忽略时,将使用用户对AE/AWB/AF_MODE的选择。
· 对SCENE_MODE_FACE_PRIORITY的场景,AE/AWB/AFMODE工作在ANDROID_CONTROL_MODE_AUTO模式下。但是3A算法需要侧重对场景中检测出来的脸进行测光和聚焦。
2、自动曝光模式下:
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
requestBuilder.set(CaptureRequest.CONTROL_MODE,
CaptureRequest.CONTROL_MODE_AUTO);
mCaptureCallback中回调的的数据对象CaptureResult:
通过CaptureResult.get(CaptureResult.SENSOR_SENSITIVITY) 可以实时获取每一帧的实时感光度。