前言
最近在做相机相关的项目,考虑到调用系统相机部分功能不能满足项目需求,于是考虑自定相机这一块,由于之前没有接触过自定义相机这一块,于是查阅了相关资料,结合自身实践,做一个简单的总结。
相关参考资料
感谢下面文章提供的参考
切入正题
1)camera2包架构示意图:
2) camera2包中的主要API结构图:
3)主要API详解:
-
CameraManager
:摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。除此之外,调用CameraManager
的getCameraCharacteristics(String)
方法即可获取指定摄像头的相关特性。 -
CameraCharacteristics
:摄像头特性。该对象通过CameraManager
来获取,用于描述特定摄像头所支持的各种特性。 -
CameraDevice
:代表系统摄像头。该类的功能类似于早期的Camera
类。 -
CameraCaptureSession
:这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session
。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest()
;控制拍照的方法为capture()
。
为了监听CameraCaptureSession
的创建过程,以及监听CameraCaptureSession
的拍照过程,Camera v2
API为CameraCaptureSession
提供了StateCallback
、CaptureCallback
等内部类。 -
CameraRequest
和CameraRequest.Builder
:当程序调用setRepeatingRequest()
方法进行预览时,或调用capture()
方法进行拍照时,都需要传入CameraRequest
参数。CameraRequest
代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CameraRequest
参数进行设置。CameraRequest.Builder
则负责生成CameraRequest
对象。
4)自定义相机拍照大致流程:
1.
调用 CameraManager
的openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)
方法打开指定摄像头。该方法的第一个参数cameraId
代表要打开的摄像头ID(摄像头ID(通常0代表后置摄像头,1代表前置摄像头);第二个参数用于监听摄像头的状态;第三个参数代表执行callback
的Handler
,如果程序希望直接在当前线程中执行callback
,则可将handler
参数设为null
。
2. 获取CameraDevice
对象
当摄像头被打开之后,程序即可获取CameraDevice
—即根据摄像头ID获取了指定摄像头设备,然后调用CameraDevice
的createCaptureSession(List<Surface> outputs, CameraCaptureSession. StateCallback callback,Handler handler)
方法来创建CameraCaptureSession
。该方法的第一个参数是一个List
集合,封装了所有需要从该摄像头获取图片的Surface
,第二个参数用于监听CameraCaptureSession
的创建过程;第三个参数代表执行callback
的Handler
,如果程序希望直接在当前线程中执行callback
,则可将handler
参数设为null
。
3. 设置设置摄像头模式
不管预览还是拍照,程序都调用CameraDevice
的createCaptureRequest(int templateType)
方法创建CaptureRequest.Builder
,该方法支持TEMPLATE_PREVIEW
(预览)、TEMPLATE_RECORD
(拍摄视频)、TEMPLATE_STILL_CAPTURE
(拍照)等参数。
通过第3步所调用方法返回的CaptureRequest.Builder
设置拍照的各种参数,比如对焦模式、曝光模式等。
调用CaptureRequest.Builder
的build()
方法即可得到CaptureRequest
对象,接下来程序可通过CameraCaptureSession
的setRepeatingRequest()
方法开始预览,或调用capture()
方法拍照。
5)案例代码
配置静态权限
6.0之后记得在代码中添加相机和读写存储卡的动态权限
<!-- 相机 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autocues"/>
<!-- 用于读写存储卡 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
相机界面简单布局
界面很简单,就一个TextureView和一个拍照按钮
activity_camera.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="net.ticp.uwonders.imagerecognize.activity.CameraActivity">
<TextureView
android:id="@+id/camera_texture_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/capture_ib"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="bottom|center"
android:layout_marginBottom="10dp"
android:background="@drawable/home_scan" />
</FrameLayout>
CameraActivity
代码实现,项目中使用了 Butterknife 简单注解
该文件注释比较清楚,相信大家能够看懂,就不分方法细讲了
/**
* @author Marlon
* @desc Camera2Activity 基于Camera2 API 自定义相机
* @date 2018/6/13
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class Camera2Activity extends AppCompatActivity {
@BindView(R.id.texture)
AutoFitTextureView texture;
@BindView(R.id.takephoto)
ImageView takephoto;
@BindView(R.id.change)
Switch change;
@BindView(R.id.auto)
RadioButton auto;
@BindView(R.id.open)
RadioButton open;
@BindView(R.id.close)
RadioButton close;
@BindView(R.id.flash_rg)
RadioGroup flashRg;
/*** 相机管理类*/
CameraManager mCameraManager;
/*** 指定摄像头ID对应的Camera实体对象*/
CameraDevice mCameraDevice;
/**
* 预览尺寸
*/
private Size mPreviewSize;
private int mSurfaceWidth;
private int mSurfaceHeight;
/*** 打开摄像头的ID{@link CameraDevice}.*/
private int mCameraId = CameraCharacteristics.LENS_FACING_FRONT;
/*** 处理静态图像捕获的ImageReader。{@link ImageReader}*/
private ImageReader mImageReader;
/*** 用于相机预览的{@Link CameraCaptureSession}。*/
private CameraCaptureSession mCaptureSession;
/*** {@link CaptureRequest.Builder}用于相机预览请求的构造器*/
private CaptureRequest.Builder mPreviewRequestBuilder;
/***预览请求, 由上面的构建器构建出来*/
private CaptureRequest mPreviewRequest;
/**
* 从屏幕旋转图片转换方向。
*/
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
/***判断是否支持闪关灯*/
private boolean mFlashSupported;
/*** 用于运行不应阻塞UI的任务的附加线程。*/
private HandlerThread mBackgroundThread;
/*** 用于在后台运行任务的{@link Handler}。*/
private Handler mBackgroundHandler;
/**
* 文件存储路径
*/
private File mFile;
/**
* 预览请求构建器, 用来构建"预览请求"(下面定义的)通过pipeline发送到Camera device
* 这是{@link ImageReader}的回调对象。 当静止图像准备保存时,将会调用“onImageAvailable”。
*/
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
}
};
/*** {@link CameraDevice.StateCallback}打开指定摄像头回调{@link CameraDevice}*/
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
createCameraPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
cameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
cameraDevice = null;
}
};
/**
* TextureView 生命周期响应
*/
private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override //创建
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//当TextureView创建完成,打开指定摄像头相机
openCamera(width, height, mCameraId);
}
@Override //尺寸改变
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override //销毁
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override //更新
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
};
private int CONTROL_AE_MODE;
/**
* 打开指定摄像头ID的相机
*
* @param width
* @param height
* @param cameraId
*/
private void openCamera(int width, int height, int cameraId) {
if (ActivityCompat.checkSelfPermission(Camera2Activity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
return;
}
try {
mSurfaceWidth = width;
mSurfaceHeight = height;
// getCameraId(cameraId);
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId + "");
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// 获取设备方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
int totalRotation = sensorToDeviceRotation(characteristics, rotation);
boolean swapRotation = totalRotation == 90 || totalRotation == 270;
int rotatedWidth = mSurfaceWidth;
int rotatedHeight = mSurfaceHeight;
if (swapRotation) {
rotatedWidth = mSurfaceHeight;
rotatedHeight = mSurfaceWidth;
}
// 获取最佳的预览尺寸
mPreviewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class), rotatedWidth, rotatedHeight);
if (swapRotation) {
texture.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
} else {
texture.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
if (mImageReader == null) {
// 创建一个ImageReader对象,用于获取摄像头的图像数据,maxImages是ImageReader一次可以访问的最大图片数量
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.JPEG, 2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
}
//检查是否支持闪光灯
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
mCameraManager.openCamera(mCameraId + "", mStateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 设置相机闪关灯模式
*
* @param AE_MODE 闪关灯的模式
* @throws CameraAccessException
*/
private void setFlashMode(int AE_MODE) {
if (mFlashSupported) {
this.CONTROL_AE_MODE = AE_MODE;
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, AE_MODE);
if (AE_MODE == CaptureRequest.CONTROL_AE_MODE_OFF) {
mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
}
}
// 构建上述的请求
mPreviewRequest = mPreviewRequestBuilder.build();
// 重复进行上面构建的请求, 用于显示预览
try {
mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 创建预览对话
*/
private void createCameraPreview() {
try {
// 获取texture实例
SurfaceTexture surfaceTexture = texture.getSurfaceTexture();
assert surfaceTexture != null;
//我们将默认缓冲区的大小配置为我们想要的相机预览的大小。
surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// 用来开始预览的输出surface
Surface surface = new Surface(surfaceTexture);
//创建预览请求构建器
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//将TextureView的Surface作为相机的预览显示输出
mPreviewRequestBuilder.addTarget(surface);
//在这里,我们为相机预览创建一个CameraCaptureSession。
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// 相机关闭时, 直接返回
if (null == mCameraDevice) {
return;
}
//会话准备就绪后,我们开始显示预览。
// 会话可行时, 将构建的会话赋给field
mCaptureSession = cameraCaptureSession;
//相机预览应该连续自动对焦。
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//设置闪关灯模式
setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
showToast("预览失败了");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 拍照时调用方法
*/
private void captureStillPicture() {
try {
if (mCameraDevice == null) {
return;
}
// 创建作为拍照的CaptureRequest.Builder
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
// 将imageReader的surface作为CaptureRequest.Builder的目标
mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
/* // 设置自动对焦模式
mBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
mBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);*/
//设置为自动模式
// mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
setFlashMode(CONTROL_AE_MODE);
// 停止连续取景
mCaptureSession.stopRepeating();
// 捕获静态图像
mCaptureSession.capture(mPreviewRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
// 拍照完成时激发该方法
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
//重新打开预览
createCameraPreview();
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 获取设备方向
*
* @param characteristics
* @param deviceOrientation
* @return
*/
private static int sensorToDeviceRotation(CameraCharacteristics characteristics, int deviceOrientation) {
int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
deviceOrientation = ORIENTATIONS.get(deviceOrientation);
return (sensorOrientation + deviceOrientation + 360) % 360;
}
/**
* 获取可用设备可用摄像头列表
*/
private void getCameraId(int ID) {
try {
for (String cameraId : mCameraManager.getCameraIdList()) {
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
if (characteristics.get(CameraCharacteristics.LENS_FACING) == ID) {
continue;
}
mCameraId = Integer.valueOf(cameraId);
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 设置最佳尺寸
*
* @param sizes
* @param width
* @param height
* @return
*/
private Size getPreferredPreviewSize(Size[] sizes, int width, int height) {
List<Size> collectorSizes = new ArrayList<>();
for (Size option : sizes) {
if (width > height) {
if (option.getWidth() > width && option.getHeight() > height) {
collectorSizes.add(option);
}
} else {
if (option.getHeight() > width && option.getWidth() > height) {
collectorSizes.add(option);
}
}
}
if (collectorSizes.size() > 0) {
return Collections.min(collectorSizes, new Comparator<Size>() {
@Override
public int compare(Size s1, Size s2) {
return Long.signum(s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getHeight());
}
});
}
return sizes[0];
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
ButterKnife.bind(this);
initView();
}
private void initView() {
change.setOnCheckedChangeListener((buttonView, isChecked) -> {
closeCamera();
if (!isChecked) {
//后置摄像头
mCameraId = CameraCharacteristics.LENS_FACING_FRONT;
if (texture.isAvailable()) {
openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
} else {
texture.setSurfaceTextureListener(textureListener);
}
} else {
//前置摄像头
mCameraId = CameraCharacteristics.LENS_FACING_BACK;
if (texture.isAvailable()) {
openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
} else {
texture.setSurfaceTextureListener(textureListener);
}
}
});
flashRg.setOnCheckedChangeListener((group, checkedId) -> {
switch (checkedId) {
case R.id.auto:
//自动闪光灯
setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
break;
case R.id.open:
//开启闪光灯
setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
break;
case R.id.close:
//关闭闪光灯
setFlashMode(CaptureRequest.CONTROL_AE_MODE_OFF);
break;
default:
break;
}
});
// 获取CameraManager 相机设备管理器
mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
}
/**
* 初试化拍照线程
*/
public void startBackgroundThread() {
mBackgroundThread = new HandlerThread("Camera Background");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
public void stopBackgroundThread() {
if (mBackgroundThread != null) {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Closes the current {@link CameraDevice}.
* 关闭正在使用的相机
*/
private void closeCamera() {
// 关闭捕获会话
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
// 关闭当前相机
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
// 关闭拍照处理器
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
}
@Override
protected void onResume() {
super.onResume();
if (texture.isAvailable()) {
openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
} else {
texture.setSurfaceTextureListener(textureListener);
}
startBackgroundThread();
}
@Override
protected void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
@OnClick(R.id.takephoto)
public void onViewClicked() {
captureStillPicture();
}
/**
* Shows a {@link Toast} on the UI thread.
* 在UI上显示Toast的方法
*
* @param text The message to show
*/
/**
* Shows a {@link Toast} on the UI thread.
* 在UI上显示Toast的方法
*
* @param text The message to show
*/
private void showToast(final String text) {
runOnUiThread(() -> Toast.makeText(Camera2Activity.this, text, Toast.LENGTH_SHORT).show());
}
/**
* Saves a JPEG {@link Image} into the specified {@link File}.
* 保存图片到自定目录
* 保存jpeg到指定的文件夹下, 开启子线程执行保存操作
*/
private static class ImageSaver implements Runnable {
/**
* The JPEG image
* 要保存的图片
*/
private final Image mImage;
/**
* The file we save the image into.
* 图片存储的路径
*/
private final File mFile;
ImageSaver(Image image, File file) {
mImage = image;
mFile = file;
}
@Override
public void run() {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
FileOutputStream output = null;
try {
output = new FileOutputStream(mFile);
output.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImage.close();
if (null != output) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
具体代码请查看项目demo地址demo
总结
本文主要总结使用Android Camera2 API 自定义相机,及实现闪光灯的打开,关闭,自动,前后摄像头的切换等功能的集成等)。
demo中包含了使用Android Camera1/ Camera2 API 自定义相机及相关功能,同时也包含了使用google框架 cameraView 自定义相机的demo,欢迎大家浏览和Star!
笔者水平有限,如有任何疑问,请大神多多赐教!