1. 概述

最近在做一些关于人脸识别的项目,需要用到 Android 相机的预览功能。网上查阅相关资料后,发现 Android 5.0 及以后的版本中,原有的 Camera API 已经被 Camera2 API 所取代。

全新的 Camera2 在 Camera 的基础上进行了改造,大幅提升了 Android 系统的拍照功能。它通过以下几个类与方法来实现相机预览时的工作过程:

•CameraManager :摄像头管理器,主要用于检测系统摄像头、打开系统摄像头等;

•CameraDevice : 用于描述系统摄像头,可用于关闭相机、创建相机会话、发送拍照请求等;

•CameraCharacteristics :用于描述摄像头所支持的各种特性;

•CameraCaptureSession :当程序需要预览、拍照时,都需要先通过 CameraCaptureSession 来实现。该会话通过调用方法 setRepeatingRequest() 实现预览;

•CameraRequest :代表一次捕获请求,用于描述捕获图片的各种参数设置;

•CameraRequest.Builder :负责生成 CameraRequest 对象。

2. 相机预览

下面通过源码来讲解如何使用 Camera2 来实现相机的预览功能。

2.1 相机权限设置

2.2 App 布局

•activity_main.xml

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/container"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="#000"

tools:context=".MainActivity">

•fragment_camera.xml

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".CameraFragment">

android:id="@+id/textureView"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerVertical="true"

android:layout_centerHorizontal="true" />

2.3 相机自定义View

public class AutoFitTextureView extends TextureView {

private int mRatioWidth = 0;

private int mRatioHeight = 0;

public AutoFitTextureView(Context context) {

this(context, null);

}

public AutoFitTextureView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

}

public void setAspectRatio(int width, int height) {

if (width < 0 || height < 0) {

throw new IllegalArgumentException("Size cannot be negative.");

}

mRatioWidth = width;

mRatioHeight = height;

requestLayout();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int width = MeasureSpec.getSize(widthMeasureSpec);

int height = MeasureSpec.getSize(heightMeasureSpec);

if (0 == mRatioWidth || 0 == mRatioHeight) {

setMeasuredDimension(width, height);

} else {

if (width < height * mRatioWidth / mRatioHeight) {

setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);

} else {

setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);

}

}

}

}

2.4 动态申请相机权限

public class MainActivity extends AppCompatActivity {

private static final int REQUEST_PERMISSION = 1;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if (hasPermission()) {

if (null == savedInstanceState) {

setFragment();

}

} else {

requestPermission();

}

}

@Override

public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {

if (requestCode == REQUEST_PERMISSION) {

if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

setFragment();

} else {

requestPermission();

}

} else {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

}

}

// 权限判断,当系统版本大于23时,才有必要判断是否获取权限

private boolean hasPermission() {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;

} else {

return true;

}

}

// 请求相机权限

private void requestPermission() {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {

Toast.makeText(MainActivity.this, "Camera permission are required for this demo", Toast.LENGTH_LONG).show();

}

requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION);

}

}

// 启动相机Fragment

private void setFragment() {

getSupportFragmentManager()

.beginTransaction()

.replace(R.id.container, CameraFragment.newInstance())

.commitNowAllowingStateLoss();

}

}

2.5 开启相机预览

首先,在onResume()中,我们需要开启一个 HandlerThread,然后利用该线程的 Looper 对象构建一个 Handler 用于相机回调。

@Override

public void onResume() {

super.onResume();

startBackgroundThread();

// When the screen is turned off and turned back on, the SurfaceTexture is

// already available, and "onSurfaceTextureAvailable" will not be called. In

// that case, we can open a camera and start preview from here (otherwise, we

// wait until the surface is ready in the SurfaceTextureListener).

if (mTextureView.isAvailable()) {

openCamera(mTextureView.getWidth(), mTextureView.getHeight());

} else {

mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);

}

}

private void startBackgroundThread() {

mBackgroundThread = new HandlerThread("CameraBackground");

mBackgroundThread.start();

mBackgroundHandler = new Handler(mBackgroundThread.getLooper());

}

同时,在 onPause() 中有对应的 HandlerThread 关闭方法。

当屏幕关闭后重新开启,SurfaceTexture 已经就绪,此时不会触发 onSurfaceTextureAvailable 回调。因此,我们判断 mTextureView 如果可用,则直接打开相机,否则等待 SurfaceTexture 回调就绪后再开启相机。

private void openCamera(int width, int height) {

if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)

!= PackageManager.PERMISSION_GRANTED) {

return;

}

setUpCameraOutputs(width, height);

configureTransform(width, height);

Activity activity = getActivity();

CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);

try {

if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {

throw new RuntimeException("Time out waiting to lock camera opening.");

}

manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);

} catch (CameraAccessException e) {

e.printStackTrace();

} catch (InterruptedException e) {

throw new RuntimeException("Interrupted while trying to lock camera opening.", e);

}

}

开启相机时,我们首先判断是否具备相机权限,然后调用 setUpCameraOutputs 函数对相机参数进行设置(包括指定摄像头、相机预览方向以及预览尺寸的设定等),接下来调用 configureTransform 函数对预览图片的大小和方向进行调整,最后获取 CameraManager 对象开启相机。因为相机有可能会被其他进程同时访问,所以在开启相机时需要加锁。

private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

@Override

public void onOpened(@NonNull CameraDevice cameraDevice) {

mCameraOpenCloseLock.release();

mCameraDevice = cameraDevice;

createCameraPreviewSession();

}

@Override

public void onDisconnected(@NonNull CameraDevice cameraDevice) {

mCameraOpenCloseLock.release();

cameraDevice.close();

mCameraDevice = null;

}

@Override

public void onError(@NonNull CameraDevice cameraDevice, int error) {

mCameraOpenCloseLock.release();

cameraDevice.close();

mCameraDevice = null;

Activity activity = getActivity();

if (null != activity) {

activity.finish();

}

}

};

相机开启时还会指定相机的状态变化回调函数 mStateCallback,如果相机成功开启,则开始创建相机预览会话。

private void createCameraPreviewSession() {

try {

// 获取 texture 实例

SurfaceTexture texture = mTextureView.getSurfaceTexture();

assert texture != null;

// 设置 TextureView 缓冲区大小

texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

// 获取 Surface 显示预览数据

Surface surface = new Surface(texture);

// 构建适合相机预览的请求

mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

// 设置 surface 作为预览数据的显示界面

mPreviewRequestBuilder.addTarget(surface);

// 创建相机捕获会话用于预览

mCameraDevice.createCaptureSession(Arrays.asList(surface),

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);

mPreviewRequest = mPreviewRequestBuilder.build();

// 设置反复捕获数据的请求,预览界面一直显示画面

mCaptureSession.setRepeatingRequest(mPreviewRequest,

null, mBackgroundHandler);

} catch (CameraAccessException e) {

e.printStackTrace();

}

}

@Override

public void onConfigureFailed(

@NonNull CameraCaptureSession cameraCaptureSession) {

showToast("Failed");

}

}, null

);

} catch (CameraAccessException e) {

e.printStackTrace();

}

}

以上便是 Camera2 API 实现相机预览的主要过程。

3. Demo 源码

4. 参考

总结

以上所述是小编给大家介绍的Android Camera2 实现预览功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐