android camera2自定义,一篇文章学会Android Camera2 Api,实现自定义相机入门。

前言

最近在做相机相关的项目,考虑到调用系统相机部分功能不能满足项目需求,于是考虑自定相机这一块,由于之前没有接触过自定义相机这一块,于是查阅了相关资料,结合自身实践,做一个简单的总结。

相关参考资料

感谢下面文章提供的参考

切入正题

1)camera2包架构示意图:

image

2) camera2包中的主要API结构图:

image

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)自定义相机拍照大致流程:

image

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 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之后记得在代码中添加相机和读写存储卡的动态权限

相机界面简单布局

界面很简单,就一个TextureView和一个拍照按钮

activity_camera.xml

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">

android:id="@+id/camera_texture_view"

android:layout_width="match_parent"

android:layout_height="match_parent" />

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" />

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 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() {

@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!

笔者水平有限,如有任何疑问,请大神多多赐教!

你可能感兴趣的:(android,camera2自定义)