Android Camera2 拍照流程
共 23749字,需浏览 48分钟
·
2021-08-05 12:18
和你一起终身学习,这里是程序员Android
经典好文推荐,通过阅读本文,您将收获以下知识点:
一、Camera 2 介绍
二、Camera2包架构示意图
三、 Camera2 的 主要类:
四、相机预览与拍照流程
一、Camera 2 介绍
camera2 API 的加入是从AndroidV5.0(21)开始的,因此我们使用Camera2应该是在Android 5.0(含5.0)之后。同时,对于Android6.0我们需要有动态权限的管理。这两点应该是使用Camera2使用前的最基本认知。Android 5.0对拍照API进行了全新的设计,新增了全新设计的Camera v2 API,这些API不仅大幅提高了Android系统拍照的功能,还能支持RAW照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。下面不做过多介绍了,直接开撸了。
二、Camera2包架构示意图
我们先来看看 camera2包架构示意图(不用一下子理解,只需要有个整体印象):
image.png
这里引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata。这一切建立在一个叫作 CameraCaptureSession 的会话中。
三、 Camera2 的 主要类:
3.1 CameraManager:
摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。另外,调用CameraManager的getCameraCharacteristics(String cameraId)方法即可获取指定摄像头的相关特性。
3.2 CameraCharacteristics:
摄像头特性。该对象通过CameraManager来获取,用于描述特定摄像头所支持的各种特性。类似与原来的CameraInfo 。
3.3 CameraDevice:
代表系统摄像头。该类的功能类似于早期的Camera类。而每个CameraDevice自己会负责创建CameraCaptureSession 以及建立 CaptureRequest。
3.4 CameraCaptureSession:
这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest();控制拍照的方法为capture()。为了监听CameraCaptureSession的创建过程,以及监听CameraCaptureSession的拍照过程,Camera-v2-API为CameraCaptureSession提供了StateCallback、CaptureCallback等内部类。
3.5 CameraRequest和CameraRequest.Builder:
当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CameraRequest参数进行设置。CameraRequest.Builder则负责生成CameraRequest对象。
四、相机预览与拍照流程
如果你看不太懂流程图,没关系,待会儿我们通过代码就可以更好的理解了。首先,Google官方推荐的Camera2控制拍照的步骤大致如下。
4.1 Camera2拍照的步骤
(1)用CameraManager的openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)方法打开指定摄像头。该方法的第一个参数代表要打开的摄像头ID;第二个参数用于监听摄像头的状态;第三个参数代表执行callback的Handler,如果程序希望直接在当前线程中执行callback,则可将handler参数设为null。
(2)当摄像头被打开之后会回调接口mStateCallback.onOpened,程序即可获取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(拍照)等参数。
(4)通过第3步所调用方法返回的CaptureRequest.Builder设置拍照的各种参数,比如对焦模式、曝光模式等。
(5)调用CaptureRequest.Builder的build()方法即可得到CaptureRequest对象,接下来程序可通过CameraCaptureSession的setRepeatingRequest()方法开始预览,或调用capture()方法拍照。相机的预览与拍照流程我们基本了解了。
(6)预览时,是将mSurfaceHolder.getSurface()作为目标,使用setRepeatingRequest()方法,显示拍照结果时,是将mImageReader.getSurface()作为目标,使用capture()方法。
然后这里还有一个大招:Google官方Camera2拍照的demo的地址:点击跳转github
4.2 首先是我们的layout代码
首先权限不能忘:
uses-permission android:name=”android.permission.CAMERA” /
uses-feature android:name=”android.hardware.camera” /
uses-feature android:name=”android.hardware.camera.autofocus” /
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/texture"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/cancelButton"
android:text="取消"
android:visibility="invisible"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/captureButton"
android:text="拍照"
android:visibility="visible"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/saveButton"
android:text="保存"
android:visibility="invisible"/>
</LinearLayout>
</LinearLayout>
4.3 实现代码
public class customCarmeraActivity extends AppCompatActivity {
private static final int REQUEST_CAMERA_PERMISSION = 1;
private static final int STATE_PREVIEW = 1;
private static final int STATE_WAITING_PRECAPTURE = 2;
private int mState = STATE_PREVIEW;
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 CameraManager mCameraManagerm;
private CameraDevice mCameraDevice;
private String mCameraId;
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private TextureView mTextureView;
private ImageReader mImageReader;
private File mFile;
private CaptureRequest.Builder mPreviewBuilder;
private CaptureRequest mPreviewRequest;
private CameraCaptureSession mCaptureSession;
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
/**
* 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();
}
}
/**
*
* @TextureView.SurfaceTextureListener:public static interface
* @onSurfaceTextureAvailable:Invoked when a TextureView's SurfaceTexture is ready for use.
* @onSurfaceTextureSizeChanged:Invoked when the SurfaceTexture's buffers size changed.
* @onSurfaceTextureDestroyed:Invoked when the specified SurfaceTexture is about to be destroyed.
* @onSurfaceTextureUpdated:Invoked when the specified SurfaceTexture is updated through updateTexImage().
*/
private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
//configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
};
/**
* Shows a {@link Toast} on the UI thread.
*
* @param text The message to show
*/
private void showToast(final String text) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(customCarmeraActivity.this, text, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Capture a still picture. This method should be called when we get a response in
* {@link #mCaptureCallback} from both {@link #lockFocus()}.
*/
private void captureStillPicture() {
try {
if (null == mCameraDevice) {
return;
}
// This is the CaptureRequest.Builder that we use to take a picture.
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());//拍照时,是将mImageReader.getSurface()作为目标
// Use the same AE and AF modes as the preview.
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// Orientation
int rotation = getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(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("customCarmeraActivity", mFile.toString());
unlockFocus();//恢复预览
}
};
mCaptureSession.stopRepeating();
mCaptureSession.abortCaptures();
mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private CameraCaptureSession.CaptureCallback mCaptureCallback =
new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
//Log.d("linc","mSessionCaptureCallback, onCaptureCompleted");
mCaptureSession = session;
checkState(result);
}
@Override
public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
CaptureResult partialResult) {
Log.d("linc","mSessionCaptureCallback, onCaptureProgressed");
mCaptureSession = session;
checkState(partialResult);
}
private void checkState(CaptureResult result) {
switch (mState) {
case STATE_PREVIEW:
// We have nothing to do when the camera preview is working normally.
break;
case STATE_WAITING_PRECAPTURE:
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;
}
}
};
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
//assert(texture != null);
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mTextureView.getWidth(), mTextureView.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewBuilder.addTarget(surface);//预览时,是将Surface()作为目标
mState = STATE_PREVIEW;
mCameraDevice.createCaptureSession(
Arrays.asList(surface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (null == mCameraDevice) {
return;
}
mCaptureSession = cameraCaptureSession;
try {
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
mPreviewRequest = mPreviewBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
Log.e("linc","set preview builder failed."+e.getMessage());
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(customCarmeraActivity.this, "Camera configuration Failed", Toast.LENGTH_SHORT).show();
}
},mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
*
* @CameraDevice.StateCallback:public static abstract class
* @onOpened:This method is called when the camera is opened. We start camera preview here.
* @onDisconnected:The method called when a camera device is no longer available for use.
* @onError:The method called when a camera device has encountered a serious error.
*/
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
};
private void getCameraId() {
try {
//Return the list of currently connected camera devices by identifier, including cameras that may be in use by other camera API clients
for (String cameraId : mCameraManagerm.getCameraIdList()) {
//Query the capabilities of a camera device
CameraCharacteristics characteristics = mCameraManagerm.getCameraCharacteristics(cameraId);
if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
mCameraId = cameraId;
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* Saves a JPEG {@link Image} into the specified {@link File}.
*/
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();
}
}
}
}
}
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//showToast("onImageAvailable: " );
mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
}
};
//TODO 执行完上面的请求权限后,系统会弹出提示框让用户选择是否允许改权限。选择的结果可以在回到接口中得知:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户允许改权限,0表示允许,-1表示拒绝 PERMISSION_GRANTED = 0, PERMISSION_DENIED = -1
//permission was granted, yay! Do the contacts-related task you need to do.
//这里进行授权被允许的处理
} else {
//permission denied, boo! Disable the functionality that depends on this permission.
//这里进行权限被拒绝的处理
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
*/
private void openCamera(int width, int height) {
//检查相机服务的访问权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
//Toast.makeText(this,"Lacking privileges to access camera service, please request permission first",Toast.LENGTH_SHORT).show();
Log.e("customCarmeraActivity.openCamera","Lacking privileges to access camera service, please request permission first");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);//API21后,向用户请求相机使用权限,然后执行onRequestPermissionsResult回调
return;
}
getCameraId();
//assert(mCameraId != null);
mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG,/*maxImages*/7);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
mCameraManagerm.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
}
/**
* Closes the current {@link CameraDevice}.
*/
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
mCameraOpenCloseLock.release();
}
}
/**
* Unlock the focus. This method should be called when still image capture sequence is
* finished.
*/
private void unlockFocus() {
try {
// Reset the auto-focus trigger
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
/* mCaptureSession.capture(mPreviewBuilder.build(), mCaptureCallback,
mBackgroundHandler);*/
// After this, the camera will go back to the normal state of preview.
mState = STATE_PREVIEW;
mCaptureSession.setRepeatingRequest(mPreviewBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* Lock the focus as the first step for a still image capture.
*/
private void lockFocus() {
try {
// This is how to tell the camera to lock focus.
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START);
// Tell #mCaptureCallback to wait for the lock.
mState = STATE_WAITING_PRECAPTURE;
mCaptureSession.capture(mPreviewBuilder.build(), mCaptureCallback,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_carmera_layout);
mCameraManagerm = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
mTextureView = (TextureView)findViewById(R.id.textureview);
mFile = new File(getExternalFilesDir(null), "pic.jpg");
Button tackPictureBtn = (Button)findViewById(R.id.takePictureButton);
tackPictureBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
//Log.i("linc", "take picture");
lockFocus();
}
});
}
@Override
protected void onResume() {
super.onResume();
startBackgroundThread();
if(mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else{
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
@Override
protected void onPause() {
super.onPause();
closeCamera();
stopBackgroundThread();
}
}
参考链接:https://blog.csdn.net/zhangzheng_1986/article/details/78710655
友情推荐:
至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!
点个在看,方便您使用时快速查找!